This is an automated email from the ASF dual-hosted git repository. morrysnow pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push: new da40e1c7670 [feature](nereids) Matiarilzed view query rewrite util implementation (#27568) da40e1c7670 is described below commit da40e1c7670492c747c284d7af7af2f874d01038 Author: seawinde <149132972+seawi...@users.noreply.github.com> AuthorDate: Tue Dec 5 11:48:04 2023 +0800 [feature](nereids) Matiarilzed view query rewrite util implementation (#27568) The basic util implementatation which is used by materialized view rewrite --- .../mv/AbstractMaterializedViewJoinRule.java | 1 + .../mv/AbstractMaterializedViewRule.java | 9 +- .../nereids/rules/exploration/mv/Predicates.java | 56 ++-- .../rules/exploration/mv/PredicatesSplitter.java | 112 ++++++++ .../rules/exploration/mv/RelationMapping.java | 63 ----- .../nereids/rules/exploration/mv/SlotMapping.java | 49 ---- .../nereids/rules/exploration/mv/StructInfo.java | 2 +- .../mv/mapping/ExpressionIndexMapping.java | 48 ++++ .../exploration/mv/{ => mapping}/Mapping.java | 61 ++-- .../exploration/mv/mapping/RelationMapping.java | 113 ++++++++ .../rules/exploration/mv/mapping/SlotMapping.java | 84 ++++++ .../expressions/visitor/ExpressionVisitors.java | 49 ---- .../plans/visitor/ExpressionLineageReplacer.java | 160 +++++++++++ .../apache/doris/nereids/util/ExpressionUtils.java | 30 +- .../nereids/rules/exploration/mv/MappingTest.java | 309 +++++++++++++++++++++ .../expression/ExpressionRewriteTestHelper.java | 2 +- .../rules/expression/PredicatesSplitterTest.java | 109 ++++++++ .../doris/nereids/util/ExpressionUtilsTest.java | 106 ++++++- 18 files changed, 1117 insertions(+), 246 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewJoinRule.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewJoinRule.java index 17df9ef88ed..899ba216e99 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewJoinRule.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewJoinRule.java @@ -17,6 +17,7 @@ package org.apache.doris.nereids.rules.exploration.mv; +import org.apache.doris.nereids.rules.exploration.mv.mapping.RelationMapping; import org.apache.doris.nereids.trees.expressions.NamedExpression; import org.apache.doris.nereids.trees.plans.Plan; import org.apache.doris.nereids.trees.plans.logical.LogicalProject; diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewRule.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewRule.java index c52acdb3fda..2dc64ccfeab 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewRule.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewRule.java @@ -20,8 +20,10 @@ package org.apache.doris.nereids.rules.exploration.mv; import org.apache.doris.catalog.TableIf; import org.apache.doris.nereids.CascadesContext; import org.apache.doris.nereids.memo.Group; -import org.apache.doris.nereids.rules.exploration.mv.Mapping.ExpressionIndexMapping; import org.apache.doris.nereids.rules.exploration.mv.Predicates.SplitPredicate; +import org.apache.doris.nereids.rules.exploration.mv.mapping.ExpressionIndexMapping; +import org.apache.doris.nereids.rules.exploration.mv.mapping.RelationMapping; +import org.apache.doris.nereids.rules.exploration.mv.mapping.SlotMapping; import org.apache.doris.nereids.trees.expressions.Expression; import org.apache.doris.nereids.trees.expressions.NamedExpression; import org.apache.doris.nereids.trees.plans.Plan; @@ -141,8 +143,9 @@ public abstract class AbstractMaterializedViewRule { targetTopExpressions, targetStructInfo.getOriginalPlan(), Sets.newHashSet(), Sets.newHashSet()); SlotMapping sourceToTargetSlotMapping = SlotMapping.generate(sourceToTargetMapping); // mv sql plan expressions transform to query based - List<? extends Expression> queryBasedExpressions = ExpressionUtils.permute(shuttledTargetExpressions, - sourceToTargetSlotMapping.inverse()); + List<? extends Expression> queryBasedExpressions = ExpressionUtils.replace( + shuttledTargetExpressions.stream().map(Expression.class::cast).collect(Collectors.toList()), + sourceToTargetSlotMapping.inverse().getSlotMap()); // mv sql query based expression and index mapping ExpressionIndexMapping.generate(queryBasedExpressions); // TODO visit source expression and replace the expression with expressionIndexMapping diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java index 40b91994be7..43aab2389ca 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java @@ -19,7 +19,6 @@ package org.apache.doris.nereids.rules.exploration.mv; import org.apache.doris.nereids.trees.expressions.Expression; import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral; -import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitors.PredicatesSpliter; import org.apache.doris.nereids.util.ExpressionUtils; import com.google.common.collect.ImmutableList; @@ -55,8 +54,7 @@ public class Predicates { * Split the expression to equal, range and residual predicate. * */ public static SplitPredicate splitPredicates(Expression expression) { - PredicatesSpliter predicatesSplit = new PredicatesSpliter(expression); - expression.accept(predicatesSplit, null); + PredicatesSplitter predicatesSplit = new PredicatesSplitter(expression); return predicatesSplit.getSplitPredicate(); } @@ -64,26 +62,26 @@ public class Predicates { * The split different representation for predicate expression, such as equal, range and residual predicate. * */ public static final class SplitPredicate { - private final Expression equalPredicates; - private final Expression rangePredicates; - private final Expression residualPredicates; - - public SplitPredicate(Expression equalPredicates, Expression rangePredicates, Expression residualPredicates) { - this.equalPredicates = equalPredicates; - this.rangePredicates = rangePredicates; - this.residualPredicates = residualPredicates; + private final Expression equalPredicate; + private final Expression rangePredicate; + private final Expression residualPredicate; + + public SplitPredicate(Expression equalPredicate, Expression rangePredicate, Expression residualPredicate) { + this.equalPredicate = equalPredicate; + this.rangePredicate = rangePredicate; + this.residualPredicate = residualPredicate; } - public Expression getEqualPredicates() { - return equalPredicates; + public Expression getEqualPredicate() { + return equalPredicate; } - public Expression getRangePredicates() { - return rangePredicates; + public Expression getRangePredicate() { + return rangePredicate; } - public Expression getResidualPredicates() { - return residualPredicates; + public Expression getResidualPredicate() { + return residualPredicate; } public static SplitPredicate empty() { @@ -103,29 +101,25 @@ public class Predicates { * isEmpty * */ public boolean isEmpty() { - return equalPredicates == null - && rangePredicates == null - && residualPredicates == null; - } - - public Expression composedExpression() { - return ExpressionUtils.and(equalPredicates, rangePredicates, residualPredicates); + return equalPredicate == null + && rangePredicate == null + && residualPredicate == null; } public List<Expression> toList() { - return ImmutableList.of(equalPredicates, rangePredicates, residualPredicates); + return ImmutableList.of(equalPredicate, rangePredicate, residualPredicate); } /** * Check the predicates in SplitPredicate is whether all true or not */ public boolean isAlwaysTrue() { - return equalPredicates instanceof BooleanLiteral - && rangePredicates instanceof BooleanLiteral - && residualPredicates instanceof BooleanLiteral - && ((BooleanLiteral) equalPredicates).getValue() - && ((BooleanLiteral) rangePredicates).getValue() - && ((BooleanLiteral) residualPredicates).getValue(); + return equalPredicate instanceof BooleanLiteral + && rangePredicate instanceof BooleanLiteral + && residualPredicate instanceof BooleanLiteral + && ((BooleanLiteral) equalPredicate).getValue() + && ((BooleanLiteral) rangePredicate).getValue() + && ((BooleanLiteral) residualPredicate).getValue(); } } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/PredicatesSplitter.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/PredicatesSplitter.java new file mode 100644 index 00000000000..e90d6583eac --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/PredicatesSplitter.java @@ -0,0 +1,112 @@ +// 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.exploration.mv; + +import org.apache.doris.nereids.trees.expressions.Alias; +import org.apache.doris.nereids.trees.expressions.Cast; +import org.apache.doris.nereids.trees.expressions.ComparisonPredicate; +import org.apache.doris.nereids.trees.expressions.CompoundPredicate; +import org.apache.doris.nereids.trees.expressions.EqualPredicate; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.Or; +import org.apache.doris.nereids.trees.expressions.SlotReference; +import org.apache.doris.nereids.trees.expressions.literal.Literal; +import org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionVisitor; +import org.apache.doris.nereids.util.ExpressionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Split the expression to equal, range and residual predicate. + * Should instance when used. + * TODO support complex predicate split + */ +public class PredicatesSplitter { + + private final List<Expression> equalPredicates = new ArrayList<>(); + private final List<Expression> rangePredicates = new ArrayList<>(); + private final List<Expression> residualPredicates = new ArrayList<>(); + private final List<Expression> conjunctExpressions; + + private final PredicateExtract instance = new PredicateExtract(); + + public PredicatesSplitter(Expression target) { + this.conjunctExpressions = ExpressionUtils.extractConjunction(target); + for (Expression expression : conjunctExpressions) { + expression.accept(instance, expression); + } + } + + /** + * PredicateExtract + */ + public class PredicateExtract extends DefaultExpressionVisitor<Void, Expression> { + + @Override + public Void visitComparisonPredicate(ComparisonPredicate comparisonPredicate, Expression sourceExpression) { + Expression leftArg = comparisonPredicate.getArgument(0); + Expression rightArg = comparisonPredicate.getArgument(1); + boolean leftArgOnlyContainsColumnRef = containOnlyColumnRef(leftArg, true); + boolean rightArgOnlyContainsColumnRef = containOnlyColumnRef(rightArg, true); + if (comparisonPredicate instanceof EqualPredicate) { + if (leftArgOnlyContainsColumnRef && rightArgOnlyContainsColumnRef) { + equalPredicates.add(comparisonPredicate); + return null; + } else { + residualPredicates.add(comparisonPredicate); + } + } else if ((leftArgOnlyContainsColumnRef && rightArg instanceof Literal) + || (rightArgOnlyContainsColumnRef && leftArg instanceof Literal)) { + rangePredicates.add(comparisonPredicate); + } else { + residualPredicates.add(comparisonPredicate); + } + return null; + } + + @Override + public Void visitCompoundPredicate(CompoundPredicate compoundPredicate, Expression context) { + if (compoundPredicate instanceof Or) { + residualPredicates.add(compoundPredicate); + return null; + } + return super.visit(compoundPredicate, context); + } + } + + public Predicates.SplitPredicate getSplitPredicate() { + return Predicates.SplitPredicate.of( + equalPredicates.isEmpty() ? null : ExpressionUtils.and(equalPredicates), + rangePredicates.isEmpty() ? null : ExpressionUtils.and(rangePredicates), + residualPredicates.isEmpty() ? null : ExpressionUtils.and(residualPredicates)); + } + + private static boolean containOnlyColumnRef(Expression expression, boolean allowCast) { + if (expression instanceof SlotReference && ((SlotReference) expression).isColumnFromTable()) { + return true; + } + if (allowCast && expression instanceof Cast) { + return containOnlyColumnRef(((Cast) expression).child(), true); + } + if (expression instanceof Alias) { + return containOnlyColumnRef(((Alias) expression).child(), true); + } + return false; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/RelationMapping.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/RelationMapping.java deleted file mode 100644 index ea91104a246..00000000000 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/RelationMapping.java +++ /dev/null @@ -1,63 +0,0 @@ -// 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.exploration.mv; - -import org.apache.doris.catalog.TableIf; -import org.apache.doris.nereids.trees.plans.algebra.CatalogRelation; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.BiMap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Multimap; - -import java.util.List; - -/** - * Relation mapping - * such as query pattern is a1 left join a2 left join b - * view pattern is a1 left join a2 left join b. the mapping will be - * [{a1:a1, a2:a2, b:b}, {a1:a2, a2:a1, b:b}] - */ -public class RelationMapping extends Mapping { - - private final BiMap<MappedRelation, MappedRelation> mappedRelationMap; - - public RelationMapping(BiMap<MappedRelation, MappedRelation> mappedRelationMap) { - this.mappedRelationMap = mappedRelationMap; - } - - public BiMap<MappedRelation, MappedRelation> getMappedRelationMap() { - return mappedRelationMap; - } - - /** - * Generate mapping according to source and target relation - */ - public static List<RelationMapping> generate(List<CatalogRelation> source, List<CatalogRelation> target) { - Multimap<TableIf, CatalogRelation> queryTableRelationIdMap = ArrayListMultimap.create(); - for (CatalogRelation relation : source) { - queryTableRelationIdMap.put(relation.getTable(), relation); - } - Multimap<TableIf, CatalogRelation> viewTableRelationIdMap = ArrayListMultimap.create(); - for (CatalogRelation relation : target) { - viewTableRelationIdMap.put(relation.getTable(), relation); - } - // todo generate relation map - return ImmutableList.of(); - } -} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/SlotMapping.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/SlotMapping.java deleted file mode 100644 index 7c50d79c6a2..00000000000 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/SlotMapping.java +++ /dev/null @@ -1,49 +0,0 @@ -// 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.exploration.mv; - -import com.google.common.collect.BiMap; - -/** - * SlotMapping, this is open generated from relationMapping - */ -public class SlotMapping extends Mapping { - - private final BiMap<MappedSlot, MappedSlot> relationSlotMap; - - public SlotMapping(BiMap<MappedSlot, MappedSlot> relationSlotMap) { - this.relationSlotMap = relationSlotMap; - } - - public BiMap<MappedSlot, MappedSlot> getRelationSlotMap() { - return relationSlotMap; - } - - public SlotMapping inverse() { - return SlotMapping.of(relationSlotMap.inverse()); - } - - public static SlotMapping of(BiMap<MappedSlot, MappedSlot> relationSlotMap) { - return new SlotMapping(relationSlotMap); - } - - public static SlotMapping generate(RelationMapping relationMapping) { - // TODO implement - return SlotMapping.of(null); - } -} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/StructInfo.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/StructInfo.java index 141b8a98bcc..ff9dee3ca60 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/StructInfo.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/StructInfo.java @@ -52,7 +52,7 @@ public class StructInfo { // construct equivalenceClass according to equals predicates this.equivalenceClass = new EquivalenceClass(); SplitPredicate splitPredicate = Predicates.splitPredicates(predicates.composedExpression()); - for (Expression expression : ExpressionUtils.extractConjunction(splitPredicate.getEqualPredicates())) { + for (Expression expression : ExpressionUtils.extractConjunction(splitPredicate.getEqualPredicate())) { EqualTo equalTo = (EqualTo) expression; equivalenceClass.addEquivalenceClass( (SlotReference) equalTo.getArguments().get(0), diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/ExpressionIndexMapping.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/ExpressionIndexMapping.java new file mode 100644 index 00000000000..f63017633a8 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/ExpressionIndexMapping.java @@ -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. + +package org.apache.doris.nereids.rules.exploration.mv.mapping; + +import org.apache.doris.nereids.trees.expressions.Expression; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; + +import java.util.List; + +/** + * Expression and it's index mapping + */ +public class ExpressionIndexMapping extends Mapping { + private final Multimap<Expression, Integer> expressionIndexMapping; + + public ExpressionIndexMapping(Multimap<Expression, Integer> expressionIndexMapping) { + this.expressionIndexMapping = expressionIndexMapping; + } + + public Multimap<Expression, Integer> getExpressionIndexMapping() { + return expressionIndexMapping; + } + + public static ExpressionIndexMapping generate(List<? extends Expression> expressions) { + Multimap<Expression, Integer> expressionIndexMapping = ArrayListMultimap.create(); + for (int i = 0; i < expressions.size(); i++) { + expressionIndexMapping.put(expressions.get(i), i); + } + return new ExpressionIndexMapping(expressionIndexMapping); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Mapping.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/Mapping.java similarity index 69% rename from fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Mapping.java rename to fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/Mapping.java index 487fc92ce50..17a412dab10 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Mapping.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/Mapping.java @@ -15,18 +15,15 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.nereids.rules.exploration.mv; +package org.apache.doris.nereids.rules.exploration.mv.mapping; import org.apache.doris.nereids.trees.expressions.ExprId; -import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.Slot; import org.apache.doris.nereids.trees.plans.RelationId; import org.apache.doris.nereids.trees.plans.algebra.CatalogRelation; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; - -import java.util.List; import java.util.Objects; +import javax.annotation.Nullable; /** * Mapping slot from query to view or inversely, @@ -38,6 +35,7 @@ public abstract class Mapping { * The relation for mapping */ public static final class MappedRelation { + public final RelationId relationId; public final CatalogRelation belongedRelation; @@ -46,7 +44,7 @@ public abstract class Mapping { this.belongedRelation = belongedRelation; } - public MappedRelation of(RelationId relationId, CatalogRelation belongedRelation) { + public static MappedRelation of(RelationId relationId, CatalogRelation belongedRelation) { return new MappedRelation(relationId, belongedRelation); } @@ -82,15 +80,31 @@ public abstract class Mapping { public static final class MappedSlot { public final ExprId exprId; + public final Slot slot; + @Nullable public final CatalogRelation belongedRelation; - public MappedSlot(ExprId exprId, CatalogRelation belongedRelation) { + public MappedSlot(ExprId exprId, + Slot slot, + CatalogRelation belongedRelation) { this.exprId = exprId; + this.slot = slot; this.belongedRelation = belongedRelation; } - public MappedSlot of(ExprId exprId, CatalogRelation belongedRelation) { - return new MappedSlot(exprId, belongedRelation); + public static MappedSlot of(ExprId exprId, + Slot slot, + CatalogRelation belongedRelation) { + return new MappedSlot(exprId, slot, belongedRelation); + } + + public static MappedSlot of(Slot slot, + CatalogRelation belongedRelation) { + return new MappedSlot(slot.getExprId(), slot, belongedRelation); + } + + public static MappedSlot of(Slot slot) { + return new MappedSlot(slot.getExprId(), slot, null); } public ExprId getExprId() { @@ -101,6 +115,10 @@ public abstract class Mapping { return belongedRelation; } + public Slot getSlot() { + return slot; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -118,27 +136,4 @@ public abstract class Mapping { return Objects.hash(exprId); } } - - /** - * Expression and it's index mapping - */ - public static class ExpressionIndexMapping extends Mapping { - private final Multimap<Expression, Integer> expressionIndexMapping; - - public ExpressionIndexMapping(Multimap<Expression, Integer> expressionIndexMapping) { - this.expressionIndexMapping = expressionIndexMapping; - } - - public Multimap<Expression, Integer> getExpressionIndexMapping() { - return expressionIndexMapping; - } - - public static ExpressionIndexMapping generate(List<? extends Expression> expressions) { - Multimap<Expression, Integer> expressionIndexMapping = ArrayListMultimap.create(); - for (int i = 0; i < expressions.size(); i++) { - expressionIndexMapping.put(expressions.get(i), i); - } - return new ExpressionIndexMapping(expressionIndexMapping); - } - } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/RelationMapping.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/RelationMapping.java new file mode 100644 index 00000000000..fdf47d13433 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/RelationMapping.java @@ -0,0 +1,113 @@ +// 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.exploration.mv.mapping; + +import org.apache.doris.catalog.TableIf; +import org.apache.doris.common.Pair; +import org.apache.doris.nereids.trees.plans.algebra.CatalogRelation; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableBiMap.Builder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Relation mapping + * such as query pattern is a1 left join a2 left join b + * view pattern is a1 left join a2 left join b. the mapping will be + * [{a1:a1, a2:a2, b:b}, {a1:a2, a2:a1, b:b}] + */ +public class RelationMapping extends Mapping { + + private final ImmutableBiMap<MappedRelation, MappedRelation> mappedRelationMap; + + public RelationMapping(ImmutableBiMap<MappedRelation, MappedRelation> mappedRelationMap) { + this.mappedRelationMap = mappedRelationMap; + } + + public BiMap<MappedRelation, MappedRelation> getMappedRelationMap() { + return mappedRelationMap; + } + + public static RelationMapping of(ImmutableBiMap<MappedRelation, MappedRelation> mappedRelationMap) { + return new RelationMapping(mappedRelationMap); + } + + /** + * Generate mapping according to source and target relation + */ + public static List<RelationMapping> generate(List<CatalogRelation> sources, List<CatalogRelation> targets) { + // Construct tmp map, key is the table qualifier, value is the corresponding catalog relations + LinkedListMultimap<Long, MappedRelation> sourceTableRelationIdMap = LinkedListMultimap.create(); + for (CatalogRelation relation : sources) { + sourceTableRelationIdMap.put(getTableQualifier(relation.getTable()), + MappedRelation.of(relation.getRelationId(), relation)); + } + LinkedListMultimap<Long, MappedRelation> targetTableRelationIdMap = LinkedListMultimap.create(); + for (CatalogRelation relation : targets) { + targetTableRelationIdMap.put(getTableQualifier(relation.getTable()), + MappedRelation.of(relation.getRelationId(), relation)); + } + Set<Long> sourceTableKeySet = sourceTableRelationIdMap.keySet(); + List<List<Pair<MappedRelation, MappedRelation>>> mappedRelations = new ArrayList<>(); + + for (Long sourceTableQualifier : sourceTableKeySet) { + List<MappedRelation> sourceMappedRelations = sourceTableRelationIdMap.get(sourceTableQualifier); + List<MappedRelation> targetMappedRelations = targetTableRelationIdMap.get(sourceTableQualifier); + if (targetMappedRelations.isEmpty()) { + continue; + } + // if source and target relation appear once, just map them + if (targetMappedRelations.size() == 1 && sourceMappedRelations.size() == 1) { + mappedRelations.add(ImmutableList.of(Pair.of(sourceMappedRelations.get(0), + targetMappedRelations.get(0)))); + continue; + } + // relation appear more than once, should cartesian them + ImmutableList<Pair<MappedRelation, MappedRelation>> relationMapping = Lists.cartesianProduct( + sourceTableRelationIdMap.get(sourceTableQualifier), targetMappedRelations) + .stream() + .map(listPair -> Pair.of(listPair.get(0), listPair.get(1))) + .collect(ImmutableList.toImmutableList()); + mappedRelations.add(relationMapping); + } + + int mappedRelationCount = mappedRelations.size(); + + return Lists.cartesianProduct(mappedRelations).stream() + .map(mappedRelationList -> { + Builder<MappedRelation, MappedRelation> mapBuilder = ImmutableBiMap.builder(); + for (int relationIndex = 0; relationIndex < mappedRelationCount; relationIndex++) { + mapBuilder.put(mappedRelationList.get(relationIndex).key(), + mappedRelationList.get(relationIndex).value()); + } + return RelationMapping.of(mapBuilder.build()); + }) + .collect(ImmutableList.toImmutableList()); + } + + private static Long getTableQualifier(TableIf tableIf) { + return tableIf.getId(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/SlotMapping.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/SlotMapping.java new file mode 100644 index 00000000000..520d6be9cdd --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/SlotMapping.java @@ -0,0 +1,84 @@ +// 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.exploration.mv.mapping; + +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.Slot; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +/** + * SlotMapping, this is open generated from relationMapping + */ +public class SlotMapping extends Mapping { + + private final BiMap<MappedSlot, MappedSlot> slotMapping; + + public SlotMapping(BiMap<MappedSlot, MappedSlot> slotMapping) { + this.slotMapping = slotMapping; + } + + public BiMap<MappedSlot, MappedSlot> getSlotBiMap() { + return slotMapping; + } + + public SlotMapping inverse() { + return slotMapping == null + ? SlotMapping.of(HashBiMap.create()) : SlotMapping.of(slotMapping.inverse()); + } + + public static SlotMapping of(BiMap<MappedSlot, MappedSlot> relationSlotMap) { + return new SlotMapping(relationSlotMap); + } + + /** + * SlotMapping, this is open generated from relationMapping + */ + @Nullable + public static SlotMapping generate(RelationMapping relationMapping) { + BiMap<MappedSlot, MappedSlot> relationSlotMap = HashBiMap.create(); + BiMap<MappedRelation, MappedRelation> mappedRelationMap = relationMapping.getMappedRelationMap(); + for (Map.Entry<MappedRelation, MappedRelation> mappedRelationEntry : mappedRelationMap.entrySet()) { + Map<String, Slot> targetNameSlotMap = + mappedRelationEntry.getValue().getBelongedRelation().getOutput().stream() + .collect(Collectors.toMap(Slot::getName, slot -> slot)); + for (Slot sourceSlot : mappedRelationEntry.getKey().getBelongedRelation().getOutput()) { + Slot targetSlot = targetNameSlotMap.get(sourceSlot.getName()); + // source slot can not map from target, bail out + if (targetSlot == null) { + return null; + } + relationSlotMap.put(MappedSlot.of(sourceSlot, mappedRelationEntry.getKey().getBelongedRelation()), + MappedSlot.of(targetSlot, mappedRelationEntry.getValue().getBelongedRelation())); + } + } + return SlotMapping.of(relationSlotMap); + } + + /** + * SlotMapping, getSlotMap + */ + public Map<? extends Expression, ? extends Expression> getSlotMap() { + return (Map) this.getSlotBiMap(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitors.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitors.java index 86949f63e16..513da0e93d9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitors.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitors.java @@ -17,16 +17,9 @@ package org.apache.doris.nereids.trees.expressions.visitor; -import org.apache.doris.nereids.rules.exploration.mv.Predicates; -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.WindowExpression; import org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction; -import org.apache.doris.nereids.util.ExpressionUtils; - -import java.util.ArrayList; -import java.util.List; /** * This is the factory for all ExpressionVisitor instance. @@ -61,46 +54,4 @@ public class ExpressionVisitors { return true; } } - - /** - * Split the expression to equal, range and residual predicate. - * Should instance when used. - */ - public static class PredicatesSpliter extends DefaultExpressionVisitor<Void, Void> { - - private List<Expression> equalPredicates = new ArrayList<>(); - private List<Expression> rangePredicates = new ArrayList<>(); - private List<Expression> residualPredicates = new ArrayList<>(); - private final Expression target; - - public PredicatesSpliter(Expression target) { - this.target = target; - } - - @Override - public Void visitComparisonPredicate(ComparisonPredicate comparisonPredicate, Void context) { - // TODO Smallest implement, complete later - if (comparisonPredicate instanceof EqualTo) { - Expression leftArgument = comparisonPredicate.getArgument(0); - Expression rightArgument = comparisonPredicate.getArgument(1); - if (leftArgument.isSlot() && rightArgument.isSlot()) { - equalPredicates.add(comparisonPredicate); - } else { - rangePredicates.add(comparisonPredicate); - } - } - return super.visit(comparisonPredicate, context); - } - - public Expression getTarget() { - return target; - } - - public Predicates.SplitPredicate getSplitPredicate() { - return Predicates.SplitPredicate.of( - equalPredicates.isEmpty() ? null : ExpressionUtils.and(equalPredicates), - rangePredicates.isEmpty() ? null : ExpressionUtils.and(rangePredicates), - residualPredicates.isEmpty() ? null : ExpressionUtils.and(residualPredicates)); - } - } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/ExpressionLineageReplacer.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/ExpressionLineageReplacer.java new file mode 100644 index 00000000000..888b538d049 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/ExpressionLineageReplacer.java @@ -0,0 +1,160 @@ +// 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.plans.visitor; + +import org.apache.doris.catalog.TableIf.TableType; +import org.apache.doris.nereids.trees.expressions.Alias; +import org.apache.doris.nereids.trees.expressions.ArrayItemReference.ArrayItemSlot; +import org.apache.doris.nereids.trees.expressions.ExprId; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.NamedExpression; +import org.apache.doris.nereids.trees.expressions.SlotReference; +import org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter; +import org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionVisitor; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.visitor.ExpressionLineageReplacer.ExpressionReplaceContext; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * ExpressionLineageReplacer + * Get from rewrite plan and can also get from plan struct info, if from plan struct info it depends on + * the nodes from graph. + */ +public class ExpressionLineageReplacer extends DefaultPlanVisitor<Expression, ExpressionReplaceContext> { + + public static final ExpressionLineageReplacer INSTANCE = new ExpressionLineageReplacer(); + + @Override + public Expression visit(Plan plan, ExpressionReplaceContext context) { + List<? extends Expression> expressions = plan.getExpressions(); + Map<ExprId, Expression> targetExpressionMap = context.getExprIdExpressionMap(); + // Filter the namedExpression used by target and collect the namedExpression + expressions.stream() + .filter(expression -> expression instanceof NamedExpression + && targetExpressionMap.containsKey(((NamedExpression) expression).getExprId())) + .forEach(expression -> expression.accept(NamedExpressionCollector.INSTANCE, context)); + return super.visit(plan, context); + } + + /** + * Replace the expression with lineage according the exprIdExpressionMap + */ + public static class ExpressionReplacer extends DefaultExpressionRewriter<Map<ExprId, Expression>> { + + public static final ExpressionReplacer INSTANCE = new ExpressionReplacer(); + + @Override + public Expression visitNamedExpression(NamedExpression namedExpression, + Map<ExprId, Expression> exprIdExpressionMap) { + if (exprIdExpressionMap.containsKey(namedExpression.getExprId())) { + return super.visit(exprIdExpressionMap.get(namedExpression.getExprId()), exprIdExpressionMap); + } + return super.visitNamedExpression(namedExpression, exprIdExpressionMap); + } + } + + /** + * The Collector for target named expressions + * TODO Collect named expression by targetTypes, tableIdentifiers + */ + public static class NamedExpressionCollector + extends DefaultExpressionVisitor<Void, ExpressionReplaceContext> { + + public static final NamedExpressionCollector INSTANCE = new NamedExpressionCollector(); + + @Override + public Void visitSlotReference(SlotReference slotReference, ExpressionReplaceContext context) { + context.getExprIdExpressionMap().put(slotReference.getExprId(), slotReference); + return super.visitSlotReference(slotReference, context); + } + + @Override + public Void visitArrayItemSlot(ArrayItemSlot arrayItemSlot, ExpressionReplaceContext context) { + context.getExprIdExpressionMap().put(arrayItemSlot.getExprId(), arrayItemSlot); + return super.visitArrayItemSlot(arrayItemSlot, context); + } + + @Override + public Void visitAlias(Alias alias, ExpressionReplaceContext context) { + // remove the alias + if (context.getExprIdExpressionMap().containsKey(alias.getExprId())) { + context.getExprIdExpressionMap().put(alias.getExprId(), alias.child()); + } + return super.visitAlias(alias, context); + } + } + + /** + * The context for replacing the expression with lineage + */ + public static class ExpressionReplaceContext { + private final List<Expression> targetExpressions; + private final Set<TableType> targetTypes; + private final Set<String> tableIdentifiers; + private Map<ExprId, Expression> exprIdExpressionMap; + private List<Expression> replacedExpressions; + + /**ExpressionReplaceContext*/ + public ExpressionReplaceContext(List<Expression> targetExpressions, + Set<TableType> targetTypes, + Set<String> tableIdentifiers) { + this.targetExpressions = targetExpressions; + this.targetTypes = targetTypes; + this.tableIdentifiers = tableIdentifiers; + // collect only named expressions and replace them with linage identifier later + this.exprIdExpressionMap = targetExpressions.stream() + .map(each -> each.collectToList(NamedExpression.class::isInstance)) + .flatMap(Collection::stream) + .map(NamedExpression.class::cast) + .collect(Collectors.toMap(NamedExpression::getExprId, expr -> expr)); + } + + public List<Expression> getTargetExpressions() { + return targetExpressions; + } + + public Set<TableType> getTargetTypes() { + return targetTypes; + } + + public Set<String> getTableIdentifiers() { + return tableIdentifiers; + } + + public Map<ExprId, Expression> getExprIdExpressionMap() { + return exprIdExpressionMap; + } + + /** + * getReplacedExpressions + */ + public List<Expression> getReplacedExpressions() { + if (this.replacedExpressions == null) { + this.replacedExpressions = targetExpressions.stream() + .map(original -> original.accept(ExpressionReplacer.INSTANCE, getExprIdExpressionMap())) + .collect(Collectors.toList()); + } + return this.replacedExpressions; + } + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java index 3149d140fe4..c44e888fbc8 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java @@ -19,7 +19,6 @@ package org.apache.doris.nereids.util; import org.apache.doris.catalog.TableIf.TableType; import org.apache.doris.nereids.CascadesContext; -import org.apache.doris.nereids.rules.exploration.mv.SlotMapping; import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext; import org.apache.doris.nereids.rules.expression.rules.FoldConstantRule; import org.apache.doris.nereids.trees.TreeNode; @@ -48,6 +47,7 @@ import org.apache.doris.nereids.trees.expressions.literal.Literal; import org.apache.doris.nereids.trees.expressions.literal.NullLiteral; import org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter; import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.visitor.ExpressionLineageReplacer; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; @@ -206,28 +206,28 @@ public class ExpressionUtils { } /** - * Replace the slot in expression with the lineage identifier from specified - * baseTable sets or target table types. - * <p> - * For example as following: + * Replace the slot in expressions with the lineage identifier from specifiedbaseTable sets or target table types + * example as following: * select a + 10 as a1, d from ( * select b - 5 as a, d from table * ); - * after shuttle a1, d in select will be b - 5 + 10, d + * op expression before is: a + 10 as a1, d. after is: b - 5 + 10, d + * todo to get from plan struct info */ - public static List<? extends Expression> shuttleExpressionWithLineage(List<? extends Expression> expression, + public static List<? extends Expression> shuttleExpressionWithLineage(List<? extends Expression> expressions, Plan plan, Set<TableType> targetTypes, Set<String> tableIdentifiers) { - return ImmutableList.of(); - } - /** - * Replace the slot in expressions according to the slotMapping - * if any slot cannot be mapped then return null - */ - public static List<? extends Expression> permute(List<? extends Expression> expressions, SlotMapping slotMapping) { - return ImmutableList.of(); + ExpressionLineageReplacer.ExpressionReplaceContext replaceContext = + new ExpressionLineageReplacer.ExpressionReplaceContext( + expressions.stream().map(NamedExpression.class::cast).collect(Collectors.toList()), + targetTypes, + tableIdentifiers); + + plan.accept(ExpressionLineageReplacer.INSTANCE, replaceContext); + // Replace expressions by expression map + return replaceContext.getReplacedExpressions(); } /** diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/MappingTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/MappingTest.java new file mode 100644 index 00000000000..39f6033b4a7 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/MappingTest.java @@ -0,0 +1,309 @@ +// 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.exploration.mv; + +import org.apache.doris.nereids.rules.exploration.mv.mapping.RelationMapping; +import org.apache.doris.nereids.rules.exploration.mv.mapping.SlotMapping; +import org.apache.doris.nereids.trees.expressions.ExprId; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.RelationId; +import org.apache.doris.nereids.trees.plans.algebra.CatalogRelation; +import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanVisitor; +import org.apache.doris.nereids.util.PlanChecker; +import org.apache.doris.utframe.TestWithFeService; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +/**MappingTest*/ +public class MappingTest extends TestWithFeService { + + @Override + protected void runBeforeAll() throws Exception { + createDatabase("mapping_test"); + useDatabase("mapping_test"); + + createTable("CREATE TABLE IF NOT EXISTS lineitem (\n" + + " L_ORDERKEY INTEGER NOT NULL,\n" + + " L_PARTKEY INTEGER NOT NULL\n" + + ")\n" + + "DUPLICATE KEY(L_ORDERKEY, L_PARTKEY)\n" + + "DISTRIBUTED BY HASH(L_ORDERKEY) BUCKETS 3\n" + + "PROPERTIES (\n" + + " \"replication_num\" = \"1\"\n" + + ")"); + createTable("CREATE TABLE IF NOT EXISTS orders (\n" + + " O_ORDERKEY INTEGER NOT NULL,\n" + + " O_CUSTKEY INTEGER NOT NULL,\n" + + " O_ORDERSTATUS CHAR(1) NOT NULL\n" + + ")\n" + + "DUPLICATE KEY(O_ORDERKEY, O_CUSTKEY)\n" + + "DISTRIBUTED BY HASH(O_ORDERKEY) BUCKETS 3\n" + + "PROPERTIES (\n" + + " \"replication_num\" = \"1\"\n" + + ")"); + createTable("CREATE TABLE IF NOT EXISTS customer (\n" + + " C_CUSTKEY INTEGER NOT NULL,\n" + + " C_NAME VARCHAR(25) NOT NULL,\n" + + ")\n" + + "DUPLICATE KEY(C_CUSTKEY, C_NAME)\n" + + "DISTRIBUTED BY HASH(C_CUSTKEY) BUCKETS 3\n" + + "PROPERTIES (\n" + + " \"replication_num\" = \"1\"\n" + + ")"); + } + + // test the num of source and target table is same + @Test + public void testGenerateMapping1() { + Plan sourcePlan = PlanChecker.from(connectContext) + .analyze("SELECT * " + + "FROM\n" + + " orders,\n" + + " lineitem,\n" + + " customer\n" + + "WHERE\n" + + " c_custkey = o_custkey\n" + + " AND l_orderkey = o_orderkey") + .getPlan(); + + Plan targetPlan = PlanChecker.from(connectContext) + .analyze("SELECT * " + + "FROM\n" + + " customer,\n" + + " orders,\n" + + " lineitem\n" + + "WHERE\n" + + " c_custkey = o_custkey\n" + + " AND l_orderkey = o_orderkey") + .getPlan(); + List<CatalogRelation> sourceRelations = new ArrayList<>(); + sourcePlan.accept(RelationCollector.INSTANCE, sourceRelations); + + List<CatalogRelation> targetRelations = new ArrayList<>(); + targetPlan.accept(RelationCollector.INSTANCE, targetRelations); + + List<RelationMapping> generateRelationMapping = RelationMapping.generate(sourceRelations, targetRelations); + Assertions.assertNotNull(generateRelationMapping); + Assertions.assertEquals(1, generateRelationMapping.size()); + + // expected slot mapping + BiMap<ExprId, ExprId> expectedSlotMapping = HashBiMap.create(); + expectedSlotMapping.put(new ExprId(0), new ExprId(2)); + expectedSlotMapping.put(new ExprId(1), new ExprId(3)); + expectedSlotMapping.put(new ExprId(2), new ExprId(4)); + expectedSlotMapping.put(new ExprId(3), new ExprId(5)); + expectedSlotMapping.put(new ExprId(4), new ExprId(6)); + expectedSlotMapping.put(new ExprId(5), new ExprId(0)); + expectedSlotMapping.put(new ExprId(6), new ExprId(1)); + // expected relation mapping + BiMap<RelationId, RelationId> expectedRelationMapping = HashBiMap.create(); + expectedRelationMapping.put(new RelationId(0), new RelationId(1)); + expectedRelationMapping.put(new RelationId(1), new RelationId(2)); + expectedRelationMapping.put(new RelationId(2), new RelationId(0)); + assertRelationMapping(generateRelationMapping.get(0), expectedRelationMapping, expectedSlotMapping); + } + + // test the num of source table is less than target table + @Test + public void testGenerateMapping2() { + Plan sourcePlan = PlanChecker.from(connectContext) + .analyze("SELECT * " + + "FROM\n" + + " orders,\n" + + " customer\n" + + "WHERE\n" + + " c_custkey = o_custkey") + .getPlan(); + + Plan targetPlan = PlanChecker.from(connectContext) + .analyze("SELECT * " + + "FROM\n" + + " customer,\n" + + " orders,\n" + + " lineitem\n" + + "WHERE\n" + + " c_custkey = o_custkey\n" + + " AND l_orderkey = o_orderkey") + .getPlan(); + List<CatalogRelation> sourceRelations = new ArrayList<>(); + sourcePlan.accept(RelationCollector.INSTANCE, sourceRelations); + + List<CatalogRelation> targetRelations = new ArrayList<>(); + targetPlan.accept(RelationCollector.INSTANCE, targetRelations); + + List<RelationMapping> generateRelationMapping = RelationMapping.generate(sourceRelations, targetRelations); + Assertions.assertNotNull(generateRelationMapping); + Assertions.assertEquals(1, generateRelationMapping.size()); + + // expected slot mapping + BiMap<ExprId, ExprId> expectedSlotMapping = HashBiMap.create(); + expectedSlotMapping.put(new ExprId(0), new ExprId(2)); + expectedSlotMapping.put(new ExprId(1), new ExprId(3)); + expectedSlotMapping.put(new ExprId(2), new ExprId(4)); + expectedSlotMapping.put(new ExprId(3), new ExprId(0)); + expectedSlotMapping.put(new ExprId(4), new ExprId(1)); + // expected relation mapping + BiMap<RelationId, RelationId> expectedRelationMapping = HashBiMap.create(); + expectedRelationMapping.put(new RelationId(0), new RelationId(1)); + expectedRelationMapping.put(new RelationId(1), new RelationId(0)); + assertRelationMapping(generateRelationMapping.get(0), expectedRelationMapping, expectedSlotMapping); + } + + // test the num of source table is more than target table + @Test + public void testGenerateMapping3() { + Plan sourcePlan = PlanChecker.from(connectContext) + .analyze("SELECT * " + + "FROM\n" + + " orders,\n" + + " lineitem,\n" + + " customer\n" + + "WHERE\n" + + " c_custkey = o_custkey\n" + + " AND l_orderkey = o_orderkey") + .getPlan(); + + Plan targetPlan = PlanChecker.from(connectContext) + .analyze("SELECT * " + + "FROM\n" + + " customer,\n" + + " orders\n" + + "WHERE\n" + + " c_custkey = o_custkey\n") + .getPlan(); + List<CatalogRelation> sourceRelations = new ArrayList<>(); + sourcePlan.accept(RelationCollector.INSTANCE, sourceRelations); + + List<CatalogRelation> targetRelations = new ArrayList<>(); + targetPlan.accept(RelationCollector.INSTANCE, targetRelations); + + List<RelationMapping> generateRelationMapping = RelationMapping.generate(sourceRelations, targetRelations); + Assertions.assertNotNull(generateRelationMapping); + Assertions.assertEquals(1, generateRelationMapping.size()); + + // expected slot mapping + BiMap<ExprId, ExprId> expectedSlotMapping = HashBiMap.create(); + expectedSlotMapping.put(new ExprId(0), new ExprId(2)); + expectedSlotMapping.put(new ExprId(1), new ExprId(3)); + expectedSlotMapping.put(new ExprId(2), new ExprId(4)); + expectedSlotMapping.put(new ExprId(5), new ExprId(0)); + expectedSlotMapping.put(new ExprId(6), new ExprId(1)); + // expected relation mapping + BiMap<RelationId, RelationId> expectedRelationMapping = HashBiMap.create(); + expectedRelationMapping.put(new RelationId(0), new RelationId(1)); + expectedRelationMapping.put(new RelationId(2), new RelationId(0)); + assertRelationMapping(generateRelationMapping.get(0), expectedRelationMapping, expectedSlotMapping); + } + + // test table of source query is repeated + @Test + public void testGenerateMapping4() { + Plan sourcePlan = PlanChecker.from(connectContext) + .analyze("SELECT orders.*, l1.* " + + "FROM\n" + + " orders,\n" + + " lineitem l1,\n" + + " lineitem l2\n" + + "WHERE\n" + + " l1.l_orderkey = l2.l_orderkey\n" + + " AND l1.l_orderkey = o_orderkey") + .getPlan(); + + Plan targetPlan = PlanChecker.from(connectContext) + .analyze("SELECT * " + + "FROM\n" + + " lineitem,\n" + + " orders\n" + + "WHERE\n" + + " l_orderkey = o_orderkey") + .getPlan(); + List<CatalogRelation> sourceRelations = new ArrayList<>(); + sourcePlan.accept(RelationCollector.INSTANCE, sourceRelations); + + List<CatalogRelation> targetRelations = new ArrayList<>(); + targetPlan.accept(RelationCollector.INSTANCE, targetRelations); + + List<RelationMapping> generateRelationMapping = RelationMapping.generate(sourceRelations, targetRelations); + Assertions.assertNotNull(generateRelationMapping); + Assertions.assertEquals(2, generateRelationMapping.size()); + + // expected slot mapping + BiMap<ExprId, ExprId> expectedSlotMapping = HashBiMap.create(); + expectedSlotMapping.put(new ExprId(0), new ExprId(2)); + expectedSlotMapping.put(new ExprId(1), new ExprId(3)); + expectedSlotMapping.put(new ExprId(2), new ExprId(4)); + expectedSlotMapping.put(new ExprId(3), new ExprId(0)); + expectedSlotMapping.put(new ExprId(4), new ExprId(1)); + // expected relation mapping + BiMap<RelationId, RelationId> expectedRelationMapping = HashBiMap.create(); + expectedRelationMapping.put(new RelationId(0), new RelationId(1)); + expectedRelationMapping.put(new RelationId(1), new RelationId(0)); + assertRelationMapping(generateRelationMapping.get(0), expectedRelationMapping, expectedSlotMapping); + + // expected slot mapping + expectedSlotMapping = HashBiMap.create(); + expectedSlotMapping.put(new ExprId(0), new ExprId(2)); + expectedSlotMapping.put(new ExprId(1), new ExprId(3)); + expectedSlotMapping.put(new ExprId(2), new ExprId(4)); + expectedSlotMapping.put(new ExprId(5), new ExprId(0)); + expectedSlotMapping.put(new ExprId(6), new ExprId(1)); + // expected relation mapping + expectedRelationMapping = HashBiMap.create(); + expectedRelationMapping.put(new RelationId(0), new RelationId(1)); + expectedRelationMapping.put(new RelationId(2), new RelationId(0)); + assertRelationMapping(generateRelationMapping.get(1), expectedRelationMapping, expectedSlotMapping); + } + + private void assertRelationMapping(RelationMapping relationMapping, + BiMap<RelationId, RelationId> expectRelationMapping, + BiMap<ExprId, ExprId> expectSlotMapping) { + // check relation mapping + BiMap<RelationId, RelationId> generatedRelationMapping = HashBiMap.create(); + relationMapping.getMappedRelationMap().forEach((key, value) -> + generatedRelationMapping.put(key.getRelationId(), value.getRelationId())); + Assertions.assertEquals(generatedRelationMapping, expectRelationMapping); + + // Generate slot mapping from relationMapping and check + SlotMapping slotMapping = SlotMapping.generate(relationMapping); + Assertions.assertNotNull(slotMapping); + BiMap<ExprId, ExprId> generatedSlotMapping = HashBiMap.create(); + slotMapping.getSlotBiMap().forEach((key, value) -> + generatedSlotMapping.put(key.getExprId(), value.getExprId()) + ); + Assertions.assertEquals(generatedSlotMapping, expectSlotMapping); + } + + protected static class RelationCollector extends DefaultPlanVisitor<Void, List<CatalogRelation>> { + + public static final RelationCollector INSTANCE = new RelationCollector(); + + @Override + public Void visit(Plan plan, List<CatalogRelation> catalogRelations) { + if (plan instanceof CatalogRelation) { + catalogRelations.add((CatalogRelation) plan); + } + return super.visit(plan, catalogRelations); + } + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/ExpressionRewriteTestHelper.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/ExpressionRewriteTestHelper.java index e8cc4dd266a..ffd3c29a18d 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/ExpressionRewriteTestHelper.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/ExpressionRewriteTestHelper.java @@ -108,7 +108,7 @@ public abstract class ExpressionRewriteTestHelper { return FunctionBinder.INSTANCE.rewrite(expression, null); } - private DataType getType(char t) { + protected DataType getType(char t) { switch (t) { case 'T': return TinyIntType.INSTANCE; diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/PredicatesSplitterTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/PredicatesSplitterTest.java new file mode 100644 index 00000000000..4a8f2e981ef --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/PredicatesSplitterTest.java @@ -0,0 +1,109 @@ +// 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; + +import org.apache.doris.catalog.Column; +import org.apache.doris.nereids.analyzer.UnboundSlot; +import org.apache.doris.nereids.rules.exploration.mv.Predicates; +import org.apache.doris.nereids.rules.exploration.mv.Predicates.SplitPredicate; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.Slot; +import org.apache.doris.nereids.trees.expressions.SlotReference; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +/** + * PredicatesSplitterTest + */ +public class PredicatesSplitterTest extends ExpressionRewriteTestHelper { + + @Test + public void testSplitPredicates() { + assetEquals("a = b and (c = d or a = 10) and a > 7 and 10 > d", + "a = b", + "a > 7 and 10 > d", + "c = d or a = 10"); + assetEquals("a = b and c + d = e and a > 7 and 10 > d", + "a = b", + "a > 7 and 10 > d", + "c + d = e"); + assetEquals("a = b and c + d = e or a > 7 and 10 > d", + "", + "", + "a = b and c + d = e or a > 7 and 10 > d"); + } + + private void assetEquals(String expression, + String expectedEqualExpr, + String expectedRangeExpr, + String expectedResidualExpr) { + + Map<String, Slot> mem = Maps.newHashMap(); + Expression targetExpr = replaceUnboundSlot(PARSER.parseExpression(expression), mem); + SplitPredicate splitPredicate = Predicates.splitPredicates(targetExpr); + + if (!StringUtils.isEmpty(expectedEqualExpr)) { + Expression equalExpression = replaceUnboundSlot(PARSER.parseExpression(expectedEqualExpr), mem); + Assertions.assertEquals(equalExpression, splitPredicate.getEqualPredicate()); + } else { + Assertions.assertNull(splitPredicate.getEqualPredicate()); + } + + if (!StringUtils.isEmpty(expectedRangeExpr)) { + Expression rangeExpression = replaceUnboundSlot(PARSER.parseExpression(expectedRangeExpr), mem); + Assertions.assertEquals(rangeExpression, splitPredicate.getRangePredicate()); + } else { + Assertions.assertNull(splitPredicate.getRangePredicate()); + } + + if (!StringUtils.isEmpty(expectedResidualExpr)) { + Expression residualExpression = replaceUnboundSlot(PARSER.parseExpression(expectedResidualExpr), mem); + Assertions.assertEquals(residualExpression, splitPredicate.getResidualPredicate()); + } else { + Assertions.assertNull(splitPredicate.getResidualPredicate()); + } + } + + @Override + public Expression replaceUnboundSlot(Expression expression, Map<String, Slot> mem) { + List<Expression> children = Lists.newArrayList(); + boolean hasNewChildren = false; + for (Expression child : expression.children()) { + Expression newChild = replaceUnboundSlot(child, mem); + if (newChild != child) { + hasNewChildren = true; + } + children.add(newChild); + } + if (expression instanceof UnboundSlot) { + String name = ((UnboundSlot) expression).getName(); + mem.putIfAbsent(name, SlotReference.fromColumn( + new Column(name, getType(name.charAt(0)).toCatalogDataType()), + Lists.newArrayList("table"))); + return mem.get(name); + } + return hasNewChildren ? expression.withChildren(children) : expression; + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/ExpressionUtilsTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/ExpressionUtilsTest.java index 55f78faa20b..7ab3e8083d1 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/ExpressionUtilsTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/ExpressionUtilsTest.java @@ -19,7 +19,10 @@ package org.apache.doris.nereids.util; import org.apache.doris.nereids.parser.NereidsParser; import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.utframe.TestWithFeService; +import com.google.common.collect.Sets; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -28,10 +31,70 @@ import java.util.List; /** * ExpressionUtils ut. */ -public class ExpressionUtilsTest { +public class ExpressionUtilsTest extends TestWithFeService { private static final NereidsParser PARSER = new NereidsParser(); + @Override + protected void runBeforeAll() throws Exception { + createDatabase("expression_test"); + useDatabase("expression_test"); + + createTable("CREATE TABLE IF NOT EXISTS lineitem (\n" + + " L_ORDERKEY INTEGER NOT NULL,\n" + + " L_PARTKEY INTEGER NOT NULL,\n" + + " L_SUPPKEY INTEGER NOT NULL,\n" + + " L_LINENUMBER INTEGER NOT NULL,\n" + + " L_QUANTITY DECIMALV3(15,2) NOT NULL,\n" + + " L_EXTENDEDPRICE DECIMALV3(15,2) NOT NULL,\n" + + " L_DISCOUNT DECIMALV3(15,2) NOT NULL,\n" + + " L_TAX DECIMALV3(15,2) NOT NULL,\n" + + " L_RETURNFLAG CHAR(1) NOT NULL,\n" + + " L_LINESTATUS CHAR(1) NOT NULL,\n" + + " L_SHIPDATE DATE NOT NULL,\n" + + " L_COMMITDATE DATE NOT NULL,\n" + + " L_RECEIPTDATE DATE NOT NULL,\n" + + " L_SHIPINSTRUCT CHAR(25) NOT NULL,\n" + + " L_SHIPMODE CHAR(10) NOT NULL,\n" + + " L_COMMENT VARCHAR(44) NOT NULL\n" + + ")\n" + + "DUPLICATE KEY(L_ORDERKEY, L_PARTKEY, L_SUPPKEY, L_LINENUMBER)\n" + + "DISTRIBUTED BY HASH(L_ORDERKEY) BUCKETS 3\n" + + "PROPERTIES (\n" + + " \"replication_num\" = \"1\"\n" + + ")"); + createTable("CREATE TABLE IF NOT EXISTS orders (\n" + + " O_ORDERKEY INTEGER NOT NULL,\n" + + " O_CUSTKEY INTEGER NOT NULL,\n" + + " O_ORDERSTATUS CHAR(1) NOT NULL,\n" + + " O_TOTALPRICE DECIMALV3(15,2) NOT NULL,\n" + + " O_ORDERDATE DATE NOT NULL,\n" + + " O_ORDERPRIORITY CHAR(15) NOT NULL, \n" + + " O_CLERK CHAR(15) NOT NULL, \n" + + " O_SHIPPRIORITY INTEGER NOT NULL,\n" + + " O_COMMENT VARCHAR(79) NOT NULL\n" + + ")\n" + + "DUPLICATE KEY(O_ORDERKEY, O_CUSTKEY)\n" + + "DISTRIBUTED BY HASH(O_ORDERKEY) BUCKETS 3\n" + + "PROPERTIES (\n" + + " \"replication_num\" = \"1\"\n" + + ")"); + createTable("CREATE TABLE IF NOT EXISTS partsupp (\n" + + " PS_PARTKEY INTEGER NOT NULL,\n" + + " PS_SUPPKEY INTEGER NOT NULL,\n" + + " PS_AVAILQTY INTEGER NOT NULL,\n" + + " PS_SUPPLYCOST DECIMALV3(15,2) NOT NULL,\n" + + " PS_COMMENT VARCHAR(199) NOT NULL \n" + + ")\n" + + "DUPLICATE KEY(PS_PARTKEY, PS_SUPPKEY)\n" + + "DISTRIBUTED BY HASH(PS_PARTKEY) BUCKETS 3\n" + + "PROPERTIES (\n" + + " \"replication_num\" = \"1\"\n" + + ")"); + connectContext.getSessionVariable().enableNereidsTimeout = false; + + } + @Test public void extractConjunctionTest() { List<Expression> expressions; @@ -93,4 +156,45 @@ public class ExpressionUtilsTest { Assertions.assertEquals(c, expressions.get(1)); Assertions.assertEquals(eAndf, expressions.get(2)); } + + @Test + public void testShuttleExpressionWithLineage1() { + PlanChecker.from(connectContext) + .checkExplain("SELECT (o.c1_abs + ps.c2_abs) as add_alias, l.L_LINENUMBER, o.O_ORDERSTATUS " + + "FROM " + + "lineitem as l " + + "LEFT JOIN " + + "(SELECT abs(O_TOTALPRICE + 10) as c1_abs, O_CUSTKEY, O_ORDERSTATUS, O_ORDERKEY " + + "FROM orders) as o " + + "ON l.L_ORDERKEY = o.O_ORDERKEY " + + "JOIN " + + "(SELECT abs(sqrt(PS_SUPPLYCOST)) as c2_abs, PS_AVAILQTY, PS_PARTKEY, PS_SUPPKEY " + + "FROM partsupp) as ps " + + "ON l.L_PARTKEY = ps.PS_PARTKEY and l.L_SUPPKEY = ps.PS_SUPPKEY", + nereidsPlanner -> { + Plan rewrittenPlan = nereidsPlanner.getRewrittenPlan(); + List<? extends Expression> originalExpressions = rewrittenPlan.getExpressions(); + List<? extends Expression> shuttledExpressions + = ExpressionUtils.shuttleExpressionWithLineage( + originalExpressions, + rewrittenPlan, + Sets.newHashSet(), + Sets.newHashSet()); + assertExpect(originalExpressions, shuttledExpressions, + "(cast(abs((cast(O_TOTALPRICE as DECIMALV3(16, 2)) + 10.00)) as " + + "DOUBLE) + abs(sqrt(cast(PS_SUPPLYCOST as DOUBLE))))", + "L_LINENUMBER", + "O_ORDERSTATUS"); + }); + } + + private void assertExpect(List<? extends Expression> originalExpressions, + List<? extends Expression> shuttledExpressions, + String... expectExpressions) { + Assertions.assertEquals(originalExpressions.size(), shuttledExpressions.size()); + Assertions.assertEquals(originalExpressions.size(), expectExpressions.length); + for (int index = 0; index < shuttledExpressions.size(); index++) { + Assertions.assertEquals(shuttledExpressions.get(index).toSql(), expectExpressions[index]); + } + } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org