Skip to content

Commit 346d1f4

Browse files
committed
return residual expr of join
1 parent d42fd68 commit 346d1f4

File tree

9 files changed

+380
-85
lines changed

9 files changed

+380
-85
lines changed

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

+100-58
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,18 +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;
55-
import java.util.Objects;
59+
import java.util.Map.Entry;
60+
import java.util.Optional;
5661
import java.util.Set;
5762
import java.util.stream.Collectors;
58-
import javax.annotation.Nullable;
5963

6064
/**
6165
* The graph is a join graph, whose node is the leaf plan and edge is a join operator.
@@ -268,11 +272,11 @@ private void makeFilterConflictRules(JoinEdge joinEdge) {
268272
filterEdges.forEach(e -> {
269273
if (LongBitmap.isSubset(e.getReferenceNodes(), leftSubNodes)
270274
&& !PushDownFilterThroughJoin.COULD_PUSH_THROUGH_LEFT.contains(joinEdge.getJoinType())) {
271-
e.addRejectJoin(joinEdge);
275+
e.addRejectEdge(joinEdge);
272276
}
273277
if (LongBitmap.isSubset(e.getReferenceNodes(), rightSubNodes)
274278
&& !PushDownFilterThroughJoin.COULD_PUSH_THROUGH_RIGHT.contains(joinEdge.getJoinType())) {
275-
e.addRejectJoin(joinEdge);
279+
e.addRejectEdge(joinEdge);
276280
}
277281
});
278282
}
@@ -289,19 +293,23 @@ private void makeJoinConflictRules(JoinEdge edgeB) {
289293
JoinEdge childA = joinEdges.get(i);
290294
if (!JoinType.isAssoc(childA.getJoinType(), edgeB.getJoinType())) {
291295
leftRequired = LongBitmap.newBitmapUnion(leftRequired, childA.getLeftSubNodes(joinEdges));
296+
childA.addRejectEdge(edgeB);
292297
}
293298
if (!JoinType.isLAssoc(childA.getJoinType(), edgeB.getJoinType())) {
294299
leftRequired = LongBitmap.newBitmapUnion(leftRequired, childA.getRightSubNodes(joinEdges));
300+
childA.addRejectEdge(edgeB);
295301
}
296302
}
297303

298304
for (int i = rightSubTreeEdges.nextSetBit(0); i >= 0; i = rightSubTreeEdges.nextSetBit(i + 1)) {
299305
JoinEdge childA = joinEdges.get(i);
300306
if (!JoinType.isAssoc(edgeB.getJoinType(), childA.getJoinType())) {
301307
rightRequired = LongBitmap.newBitmapUnion(rightRequired, childA.getRightSubNodes(joinEdges));
308+
childA.addRejectEdge(edgeB);
302309
}
303310
if (!JoinType.isRAssoc(edgeB.getJoinType(), childA.getJoinType())) {
304311
rightRequired = LongBitmap.newBitmapUnion(rightRequired, childA.getLeftSubNodes(joinEdges));
312+
childA.addRejectEdge(edgeB);
305313
}
306314
}
307315
edgeB.setLeftExtendedNodes(leftRequired);
@@ -593,57 +601,75 @@ public int edgeSize() {
593601
* compare hypergraph
594602
*
595603
* @param viewHG the compared hyper graph
596-
* @return null represents not compatible, or return some expression which can
597-
* be pull up from this hyper graph
604+
* @return Comparison result
598605
*/
599-
public @Nullable List<Expression> isLogicCompatible(HyperGraph viewHG, LogicalCompatibilityContext ctx) {
600-
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());
601609

602-
// All edge in view must have a mapped edge in query
603-
if (queryToView.size() != viewHG.edgeSize()) {
604-
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;
605615
}
616+
builder.addComparisonResult(edgeCompareRes);
606617

607-
boolean allMatch = queryToView.entrySet().stream()
608-
.allMatch(entry ->
609-
compareEdgeWithNode(entry.getKey(), entry.getValue(), ctx.getQueryToViewNodeIDMapping()));
610-
if (!allMatch) {
611-
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;
612621
}
613622

614-
// join edges must be identical
615-
boolean isJoinIdentical = joinEdges.stream()
616-
.allMatch(queryToView::containsKey);
617-
if (!isJoinIdentical) {
618-
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;
619628
}
629+
builder.addQueryExpressions(residualQueryJoin);
620630

621-
// extract all top filters
622-
List<FilterEdge> residualFilterEdges = filterEdges.stream()
623-
.filter(e -> !queryToView.containsKey(e))
624-
.collect(ImmutableList.toImmutableList());
625-
if (residualFilterEdges.stream().anyMatch(e -> !e.isTopFilter())) {
626-
return null;
631+
List<Expression> residualQueryFilter =
632+
processOrphanEdges(Sets.difference(Sets.newHashSet(filterEdges), queryToView.keySet()));
633+
if (residualQueryFilter == null) {
634+
return ComparisonResult.INVALID;
627635
}
628-
return residualFilterEdges.stream()
629-
.flatMap(e -> e.getExpressions().stream())
630-
.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();
631647
}
632648

633-
private Map<Edge, Edge> constructEdgeMap(HyperGraph viewHG, Map<Expression, Expression> exprMap) {
634-
Map<Expression, Edge> exprToEdge = constructExprMap(viewHG);
635-
Map<Edge, Edge> queryToView = new HashMap<>();
636-
joinEdges.stream()
637-
.filter(e -> !e.getExpressions().isEmpty()
638-
&& exprMap.containsKey(e.getExpression(0))
639-
&& compareEdgeWithExpr(e, exprToEdge.get(exprMap.get(e.getExpression(0))), exprMap))
640-
.forEach(e -> queryToView.put(e, exprToEdge.get(exprMap.get(e.getExpression(0)))));
641-
filterEdges.stream()
642-
.filter(e -> !e.getExpressions().isEmpty()
643-
&& exprMap.containsKey(e.getExpression(0))
644-
&& compareEdgeWithExpr(e, exprToEdge.get(exprMap.get(e.getExpression(0))), exprMap))
645-
.forEach(e -> queryToView.put(e, exprToEdge.get(exprMap.get(e.getExpression(0)))));
646-
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();
647673
}
648674

649675
private boolean compareEdgeWithNode(Edge t, Edge o, Map<Integer, Integer> nodeMap) {
@@ -686,24 +712,40 @@ private boolean compareNodeMap(long bitmap1, long bitmap2, Map<Integer, Integer>
686712
return bitmap2 == newBitmap1;
687713
}
688714

689-
private boolean compareEdgeWithExpr(Edge t, Edge o, Map<Expression, Expression> expressionMap) {
690-
if (t.getExpressions().size() != o.getExpressions().size()) {
691-
return false;
692-
}
693-
int size = t.getExpressions().size();
694-
for (int i = 0; i < size; i++) {
695-
if (!Objects.equals(expressionMap.get(t.getExpression(i)), o.getExpression(i))) {
696-
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;
697722
}
723+
builder.addComparisonResult(res);
698724
}
699-
return true;
725+
return builder.build();
700726
}
701727

702-
private Map<Expression, Edge> constructExprMap(HyperGraph hyperGraph) {
703-
Map<Expression, Edge> exprToEdge = new HashMap<>();
704-
hyperGraph.joinEdges.forEach(edge -> edge.getExpressions().forEach(expr -> exprToEdge.put(expr, edge)));
705-
hyperGraph.filterEdges.forEach(edge -> edge.getExpressions().forEach(expr -> exprToEdge.put(expr, edge)));
706-
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);
707749
}
708750

709751
/**

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
@@ -136,12 +136,14 @@ protected List<Plan> rewrite(Plan queryPlan, CascadesContext cascadesContext) {
136136
LogicalCompatibilityContext compatibilityContext =
137137
LogicalCompatibilityContext.from(queryToViewTableMapping, queryToViewSlotMapping,
138138
queryStructInfo, viewStructInfo);
139-
List<Expression> pulledUpExpressions = StructInfo.isGraphLogicalEquals(queryStructInfo, viewStructInfo,
139+
ComparisonResult comparisonResult = StructInfo.isGraphLogicalEquals(queryStructInfo, viewStructInfo,
140140
compatibilityContext);
141-
if (pulledUpExpressions == null) {
141+
if (comparisonResult.isInvalid()) {
142142
logger.debug(currentClassName + " graph logical is not equals so continue");
143143
continue;
144144
}
145+
// TODO: Use set of list? And consider view expr
146+
List<Expression> pulledUpExpressions = ImmutableList.copyOf(comparisonResult.getQueryExpressions());
145147
// set pulled up expression to queryStructInfo predicates and update related predicates
146148
if (!pulledUpExpressions.isEmpty()) {
147149
queryStructInfo.addPredicates(pulledUpExpressions);

0 commit comments

Comments
 (0)