Skip to content

Commit 2090fea

Browse files
committed
return residual expr of join
1 parent 8759bce commit 2090fea

File tree

9 files changed

+380
-84
lines changed

9 files changed

+380
-84
lines changed

fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/HyperGraph.java

+100-57
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.doris.nereids.jobs.joinorder.hypergraph.node.StructInfoNode;
2828
import org.apache.doris.nereids.memo.Group;
2929
import org.apache.doris.nereids.memo.GroupExpression;
30+
import org.apache.doris.nereids.rules.exploration.mv.ComparisonResult;
3031
import org.apache.doris.nereids.rules.exploration.mv.LogicalCompatibilityContext;
3132
import org.apache.doris.nereids.rules.rewrite.PushDownFilterThroughJoin;
3233
import org.apache.doris.nereids.trees.expressions.Alias;
@@ -44,17 +45,21 @@
4445

4546
import com.google.common.base.Preconditions;
4647
import com.google.common.collect.ImmutableList;
48+
import com.google.common.collect.ImmutableMap;
4749
import com.google.common.collect.ImmutableSet;
4850
import com.google.common.collect.Lists;
51+
import com.google.common.collect.Sets;
4952

5053
import java.util.ArrayList;
5154
import java.util.BitSet;
5255
import java.util.HashMap;
56+
import java.util.HashSet;
5357
import java.util.List;
5458
import java.util.Map;
59+
import java.util.Map.Entry;
60+
import java.util.Optional;
5561
import java.util.Set;
5662
import java.util.stream.Collectors;
57-
import javax.annotation.Nullable;
5863

5964
/**
6065
* The graph is a join graph, whose node is the leaf plan and edge is a join operator.
@@ -267,11 +272,11 @@ private void makeFilterConflictRules(JoinEdge joinEdge) {
267272
filterEdges.forEach(e -> {
268273
if (LongBitmap.isSubset(e.getReferenceNodes(), leftSubNodes)
269274
&& !PushDownFilterThroughJoin.COULD_PUSH_THROUGH_LEFT.contains(joinEdge.getJoinType())) {
270-
e.addRejectJoin(joinEdge);
275+
e.addRejectEdge(joinEdge);
271276
}
272277
if (LongBitmap.isSubset(e.getReferenceNodes(), rightSubNodes)
273278
&& !PushDownFilterThroughJoin.COULD_PUSH_THROUGH_RIGHT.contains(joinEdge.getJoinType())) {
274-
e.addRejectJoin(joinEdge);
279+
e.addRejectEdge(joinEdge);
275280
}
276281
});
277282
}
@@ -288,19 +293,23 @@ private void makeJoinConflictRules(JoinEdge edgeB) {
288293
JoinEdge childA = joinEdges.get(i);
289294
if (!JoinType.isAssoc(childA.getJoinType(), edgeB.getJoinType())) {
290295
leftRequired = LongBitmap.newBitmapUnion(leftRequired, childA.getLeftSubNodes(joinEdges));
296+
childA.addRejectEdge(edgeB);
291297
}
292298
if (!JoinType.isLAssoc(childA.getJoinType(), edgeB.getJoinType())) {
293299
leftRequired = LongBitmap.newBitmapUnion(leftRequired, childA.getRightSubNodes(joinEdges));
300+
childA.addRejectEdge(edgeB);
294301
}
295302
}
296303

297304
for (int i = rightSubTreeEdges.nextSetBit(0); i >= 0; i = rightSubTreeEdges.nextSetBit(i + 1)) {
298305
JoinEdge childA = joinEdges.get(i);
299306
if (!JoinType.isAssoc(edgeB.getJoinType(), childA.getJoinType())) {
300307
rightRequired = LongBitmap.newBitmapUnion(rightRequired, childA.getRightSubNodes(joinEdges));
308+
childA.addRejectEdge(edgeB);
301309
}
302310
if (!JoinType.isRAssoc(edgeB.getJoinType(), childA.getJoinType())) {
303311
rightRequired = LongBitmap.newBitmapUnion(rightRequired, childA.getLeftSubNodes(joinEdges));
312+
childA.addRejectEdge(edgeB);
304313
}
305314
}
306315
edgeB.setLeftExtendedNodes(leftRequired);
@@ -592,57 +601,75 @@ public int edgeSize() {
592601
* compare hypergraph
593602
*
594603
* @param viewHG the compared hyper graph
595-
* @return null represents not compatible, or return some expression which can
596-
* be pull up from this hyper graph
604+
* @return Comparison result
597605
*/
598-
public @Nullable List<Expression> isLogicCompatible(HyperGraph viewHG, LogicalCompatibilityContext ctx) {
599-
Map<Edge, Edge> queryToView = constructEdgeMap(viewHG, ctx.getQueryToViewEdgeExpressionMapping());
606+
public ComparisonResult isLogicCompatible(HyperGraph viewHG, LogicalCompatibilityContext ctx) {
607+
// 1 try to construct a map which can be mapped from edge to edge
608+
Map<Edge, Edge> queryToView = constructMapWithNode(viewHG, ctx.getQueryToViewNodeIDMapping());
600609

601-
// All edge in view must have a mapped edge in query
602-
if (queryToView.size() != viewHG.edgeSize()) {
603-
return null;
610+
// 2. compare them by expression and extract residual expr
611+
ComparisonResult.Builder builder = new ComparisonResult.Builder();
612+
ComparisonResult edgeCompareRes = compareEdgesWithExpr(queryToView, ctx.getQueryToViewEdgeExpressionMapping());
613+
if (edgeCompareRes.isInvalid()) {
614+
return ComparisonResult.INVALID;
604615
}
616+
builder.addComparisonResult(edgeCompareRes);
605617

606-
boolean allMatch = queryToView.entrySet().stream()
607-
.allMatch(entry ->
608-
compareEdgeWithNode(entry.getKey(), entry.getValue(), ctx.getQueryToViewNodeIDMapping()));
609-
if (!allMatch) {
610-
return null;
618+
// 3. pull join edge of view is no sense, so reject them
619+
if (!queryToView.values().containsAll(viewHG.joinEdges)) {
620+
return ComparisonResult.INVALID;
611621
}
612622

613-
// join edges must be identical
614-
boolean isJoinIdentical = joinEdges.stream()
615-
.allMatch(queryToView::containsKey);
616-
if (!isJoinIdentical) {
617-
return null;
623+
// 4. process residual edges
624+
List<Expression> residualQueryJoin =
625+
processOrphanEdges(Sets.difference(Sets.newHashSet(joinEdges), queryToView.keySet()));
626+
if (residualQueryJoin == null) {
627+
return ComparisonResult.INVALID;
618628
}
629+
builder.addQueryExpressions(residualQueryJoin);
619630

620-
// extract all top filters
621-
List<FilterEdge> residualFilterEdges = filterEdges.stream()
622-
.filter(e -> !queryToView.containsKey(e))
623-
.collect(ImmutableList.toImmutableList());
624-
if (residualFilterEdges.stream().anyMatch(e -> !e.isTopFilter())) {
625-
return null;
631+
List<Expression> residualQueryFilter =
632+
processOrphanEdges(Sets.difference(Sets.newHashSet(filterEdges), queryToView.keySet()));
633+
if (residualQueryFilter == null) {
634+
return ComparisonResult.INVALID;
626635
}
627-
return residualFilterEdges.stream()
628-
.flatMap(e -> e.getExpressions().stream())
629-
.collect(ImmutableList.toImmutableList());
636+
builder.addQueryExpressions(residualQueryFilter);
637+
638+
List<Expression> residualViewFilter =
639+
processOrphanEdges(
640+
Sets.difference(Sets.newHashSet(viewHG.filterEdges), Sets.newHashSet(queryToView.values())));
641+
if (residualViewFilter == null) {
642+
return ComparisonResult.INVALID;
643+
}
644+
builder.addViewExpressions(residualViewFilter);
645+
646+
return builder.build();
630647
}
631648

632-
private Map<Edge, Edge> constructEdgeMap(HyperGraph viewHG, Map<Expression, Expression> exprMap) {
633-
Map<Expression, Edge> exprToEdge = constructExprMap(viewHG);
634-
Map<Edge, Edge> queryToView = new HashMap<>();
635-
joinEdges.stream()
636-
.filter(e -> !e.getExpressions().isEmpty()
637-
&& exprMap.containsKey(e.getExpression(0))
638-
&& compareEdgeWithExpr(e, exprToEdge.get(exprMap.get(e.getExpression(0))), exprMap))
639-
.forEach(e -> queryToView.put(e, exprToEdge.get(exprMap.get(e.getExpression(0)))));
640-
filterEdges.stream()
641-
.filter(e -> !e.getExpressions().isEmpty()
642-
&& exprMap.containsKey(e.getExpression(0))
643-
&& compareEdgeWithExpr(e, exprToEdge.get(exprMap.get(e.getExpression(0))), exprMap))
644-
.forEach(e -> queryToView.put(e, exprToEdge.get(exprMap.get(e.getExpression(0)))));
645-
return queryToView;
649+
private List<Expression> processOrphanEdges(Set<Edge> edges) {
650+
List<Expression> expressions = new ArrayList<>();
651+
for (Edge edge : edges) {
652+
if (!edge.canPullUp()) {
653+
return null;
654+
}
655+
expressions.addAll(edge.getExpressions());
656+
}
657+
return expressions;
658+
}
659+
660+
private Map<Edge, Edge> constructMapWithNode(HyperGraph viewHG, Map<Integer, Integer> nodeMap) {
661+
// TODO use hash map to reduce loop
662+
Map<Edge, Edge> joinEdgeMap = joinEdges.stream().map(qe -> {
663+
Optional<JoinEdge> viewEdge = viewHG.joinEdges.stream()
664+
.filter(ve -> compareEdgeWithNode(qe, ve, nodeMap)).findFirst();
665+
return Pair.of(qe, viewEdge);
666+
}).filter(e -> e.second.isPresent()).collect(ImmutableMap.toImmutableMap(p -> p.first, p -> p.second.get()));
667+
Map<Edge, Edge> filterEdgeMap = filterEdges.stream().map(qe -> {
668+
Optional<FilterEdge> viewEdge = viewHG.filterEdges.stream()
669+
.filter(ve -> compareEdgeWithNode(qe, ve, nodeMap)).findFirst();
670+
return Pair.of(qe, viewEdge);
671+
}).filter(e -> e.second.isPresent()).collect(ImmutableMap.toImmutableMap(p -> p.first, p -> p.second.get()));
672+
return ImmutableMap.<Edge, Edge>builder().putAll(joinEdgeMap).putAll(filterEdgeMap).build();
646673
}
647674

648675
private boolean compareEdgeWithNode(Edge t, Edge o, Map<Integer, Integer> nodeMap) {
@@ -685,24 +712,40 @@ private boolean compareNodeMap(long bitmap1, long bitmap2, Map<Integer, Integer>
685712
return bitmap2 == newBitmap1;
686713
}
687714

688-
private boolean compareEdgeWithExpr(Edge t, Edge o, Map<Expression, Expression> expressionMap) {
689-
if (t.getExpressions().size() != o.getExpressions().size()) {
690-
return false;
691-
}
692-
int size = t.getExpressions().size();
693-
for (int i = 0; i < size; i++) {
694-
if (!expressionMap.get(t.getExpression(i)).equals(o.getExpression(i))) {
695-
return false;
715+
private ComparisonResult compareEdgesWithExpr(Map<Edge, Edge> queryToViewedgeMap,
716+
Map<Expression, Expression> queryToView) {
717+
ComparisonResult.Builder builder = new ComparisonResult.Builder();
718+
for (Entry<Edge, Edge> e : queryToViewedgeMap.entrySet()) {
719+
ComparisonResult res = compareEdgeWithExpr(e.getKey(), e.getValue(), queryToView);
720+
if (res.isInvalid()) {
721+
return ComparisonResult.INVALID;
696722
}
723+
builder.addComparisonResult(res);
697724
}
698-
return true;
725+
return builder.build();
699726
}
700727

701-
private Map<Expression, Edge> constructExprMap(HyperGraph hyperGraph) {
702-
Map<Expression, Edge> exprToEdge = new HashMap<>();
703-
hyperGraph.joinEdges.forEach(edge -> edge.getExpressions().forEach(expr -> exprToEdge.put(expr, edge)));
704-
hyperGraph.filterEdges.forEach(edge -> edge.getExpressions().forEach(expr -> exprToEdge.put(expr, edge)));
705-
return exprToEdge;
728+
private ComparisonResult compareEdgeWithExpr(Edge query, Edge view, Map<Expression, Expression> queryToView) {
729+
Set<? extends Expression> queryExprSet = query.getExpressionSet();
730+
Set<? extends Expression> viewExprSet = view.getExpressionSet();
731+
732+
Set<Expression> equalViewExpr = new HashSet<>();
733+
List<Expression> residualQueryExpr = new ArrayList<>();
734+
for (Expression queryExpr : queryExprSet) {
735+
if (queryToView.containsKey(queryExpr) && viewExprSet.contains(queryToView.get(queryExpr))) {
736+
equalViewExpr.add(queryToView.get(queryExpr));
737+
} else {
738+
residualQueryExpr.add(queryExpr);
739+
}
740+
}
741+
List<Expression> residualViewExpr = ImmutableList.copyOf(Sets.difference(viewExprSet, equalViewExpr));
742+
if (!residualViewExpr.isEmpty() && !view.canPullUp()) {
743+
return ComparisonResult.INVALID;
744+
}
745+
if (!residualQueryExpr.isEmpty() && !query.canPullUp()) {
746+
return ComparisonResult.INVALID;
747+
}
748+
return new ComparisonResult(residualQueryExpr, residualViewExpr);
706749
}
707750

708751
/**

fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/edge/Edge.java

+22
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.apache.doris.nereids.trees.expressions.Expression;
2323
import org.apache.doris.nereids.trees.expressions.Slot;
2424

25+
import com.google.common.collect.ImmutableSet;
26+
2527
import java.util.BitSet;
2628
import java.util.List;
2729
import java.util.Set;
@@ -51,6 +53,8 @@ public abstract class Edge {
5153
// record all sub nodes behind in this operator. It's T function in paper
5254
private final long subTreeNodes;
5355

56+
private long rejectNodes = 0;
57+
5458
/**
5559
* Create simple edge.
5660
*/
@@ -71,6 +75,10 @@ public boolean isSimple() {
7175
return LongBitmap.getCardinality(leftExtendedNodes) == 1 && LongBitmap.getCardinality(rightExtendedNodes) == 1;
7276
}
7377

78+
public void addRejectEdge(Edge edge) {
79+
rejectNodes = LongBitmap.newBitmapUnion(edge.getReferenceNodes(), rejectNodes);
80+
}
81+
7482
public void addLeftExtendNode(long left) {
7583
this.leftExtendedNodes = LongBitmap.or(this.leftExtendedNodes, left);
7684
}
@@ -171,6 +179,20 @@ public double getSelectivity() {
171179

172180
public abstract List<? extends Expression> getExpressions();
173181

182+
public Set<? extends Expression> getExpressionSet() {
183+
return ImmutableSet.copyOf(getExpressions());
184+
}
185+
186+
public boolean canPullUp() {
187+
// Only inner join and filter with none rejectNodes can be pull up
188+
return rejectNodes == 0
189+
&& !(this instanceof JoinEdge && !((JoinEdge) this).getJoinType().isInnerJoin());
190+
}
191+
192+
public long getRejectNodes() {
193+
return rejectNodes;
194+
}
195+
174196
public Expression getExpression(int i) {
175197
return getExpressions().get(i);
176198
}

fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/edge/FilterEdge.java

-15
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import org.apache.doris.nereids.trees.plans.Plan;
2323
import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
2424

25-
import java.util.ArrayList;
2625
import java.util.BitSet;
2726
import java.util.List;
2827
import java.util.Set;
@@ -32,25 +31,11 @@
3231
*/
3332
public class FilterEdge extends Edge {
3433
private final LogicalFilter<? extends Plan> filter;
35-
private final List<Integer> rejectEdges;
3634

3735
public FilterEdge(LogicalFilter<? extends Plan> filter, int index,
3836
BitSet childEdges, long subTreeNodes, long childRequireNodes) {
3937
super(index, childEdges, new BitSet(), subTreeNodes, childRequireNodes, 0L);
4038
this.filter = filter;
41-
rejectEdges = new ArrayList<>();
42-
}
43-
44-
public void addRejectJoin(JoinEdge joinEdge) {
45-
rejectEdges.add(joinEdge.getIndex());
46-
}
47-
48-
public List<Integer> getRejectEdges() {
49-
return rejectEdges;
50-
}
51-
52-
public boolean isTopFilter() {
53-
return rejectEdges.isEmpty();
5439
}
5540

5641
@Override

fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewRule.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,14 @@ protected List<Plan> rewrite(Plan queryPlan, CascadesContext cascadesContext) {
142142
LogicalCompatibilityContext compatibilityContext =
143143
LogicalCompatibilityContext.from(queryToViewTableMapping, queryToViewSlotMapping,
144144
queryStructInfo, viewStructInfo);
145-
List<Expression> pulledUpExpressions = StructInfo.isGraphLogicalEquals(queryStructInfo, viewStructInfo,
145+
ComparisonResult comparisonResult = StructInfo.isGraphLogicalEquals(queryStructInfo, viewStructInfo,
146146
compatibilityContext);
147-
if (pulledUpExpressions == null) {
147+
if (comparisonResult.isInvalid()) {
148148
logger.debug(currentClassName + " graph logical is not equals so continue");
149149
continue;
150150
}
151+
// TODO: Use set of list? And consider view expr
152+
List<Expression> pulledUpExpressions = ImmutableList.copyOf(comparisonResult.getQueryExpressions());
151153
// set pulled up expression to queryStructInfo predicates and update related predicates
152154
if (!pulledUpExpressions.isEmpty()) {
153155
queryStructInfo.addPredicates(pulledUpExpressions);

0 commit comments

Comments
 (0)