This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.0 by this push:
new 7ec36a24c8a [opt](mtmv) Support materialized view query rewriting by
join eliminate when mv use more table than query #56266 (#58108)
7ec36a24c8a is described below
commit 7ec36a24c8ac95eef2455141e2ea54496901ccc0
Author: seawinde <[email protected]>
AuthorDate: Wed Nov 26 09:41:16 2025 +0800
[opt](mtmv) Support materialized view query rewriting by join eliminate
when mv use more table than query #56266 (#58108)
### What problem does this PR solve?
pr: #56266
commitId: 12d2aaa9
Issue Number: close #xxx
Related PR: #xxx
Problem Summary:
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [ ] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
Co-authored-by: zfr9527 <[email protected]>
---
.../doris/nereids/jobs/executor/Rewriter.java | 5 +
.../joinorder/hypergraph/bitmap/LongBitmap.java | 5 +
.../org/apache/doris/nereids/rules/RuleType.java | 1 -
.../mv/AbstractMaterializedViewRule.java | 29 +-
.../mv/AbstractMaterializedViewScanRule.java | 7 +
.../rules/exploration/mv/HyperGraphComparator.java | 95 +++-
.../exploration/mv/MaterializedViewUtils.java | 19 +-
.../exploration/mv/mapping/ExpressionMapping.java | 10 +-
.../rules/exploration/mv/mapping/SlotMapping.java | 2 +-
.../plans/visitor/ExpressionLineageReplacer.java | 7 +
.../apache/doris/nereids/util/ExpressionUtils.java | 27 +
.../rules/exploration/mv/BuildStructInfoTest.java | 107 ++++
.../exploration/mv/HyperGraphComparatorTest.java | 47 +-
.../doris/nereids/util/ExpressionUtilsTest.java | 46 ++
.../join_elim_line_pattern.groovy | 558 +++++++++++++++++++
.../join_elim_star_pattern.groovy | 592 +++++++++++++++++++++
16 files changed, 1507 insertions(+), 50 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
index 9aff8ff582d..a9e28a2af0c 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
@@ -385,6 +385,11 @@ public class Rewriter extends AbstractBatchJobExecutor {
new CollectFilterAboveConsumer(),
new CollectCteConsumerOutput())
),
+ topic("eliminate join according unique or foreign
key",
+ cascadesContext ->
cascadesContext.rewritePlanContainsTypes(LogicalJoin.class),
+ bottomUp(new EliminateJoinByFK()),
+ topDown(new EliminateJoinByUnique())
+ ),
topic("Table/Physical optimization",
topDown(
new PruneOlapScanPartition(),
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/bitmap/LongBitmap.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/bitmap/LongBitmap.java
index 4365ec9ae20..32aa78393b7 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/bitmap/LongBitmap.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/bitmap/LongBitmap.java
@@ -155,6 +155,11 @@ public class LongBitmap {
return SIZE - Long.numberOfLeadingZeros(newBitmap) - 1;
}
+ // return true if bitmap1 contains all bits in bitmap2
+ public static boolean containAll(long bitmap1, long bitmap2) {
+ return (bitmap1 & bitmap2) == bitmap2;
+ }
+
public static int lowestOneIndex(long bitmap) {
return Long.numberOfTrailingZeros(bitmap);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
index 00a9174f93e..61790063917 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
@@ -278,7 +278,6 @@ public enum RuleType {
ELIMINATE_LIMIT_ON_EMPTY_RELATION(RuleTypeClass.REWRITE),
ELIMINATE_FILTER(RuleTypeClass.REWRITE),
ELIMINATE_JOIN(RuleTypeClass.REWRITE),
- ELIMINATE_JOIN_BY_FOREIGN_KEY(RuleTypeClass.REWRITE),
ELIMINATE_CONST_JOIN_CONDITION(RuleTypeClass.REWRITE),
ELIMINATE_JOIN_CONDITION(RuleTypeClass.REWRITE),
ELIMINATE_FILTER_ON_ONE_RELATION(RuleTypeClass.REWRITE),
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 dfedd782f85..6668b363897 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
@@ -18,7 +18,6 @@
package org.apache.doris.nereids.rules.exploration.mv;
import org.apache.doris.catalog.MTMV;
-import org.apache.doris.catalog.constraint.TableIdentifier;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.Id;
import org.apache.doris.common.Pair;
@@ -55,6 +54,7 @@ import
org.apache.doris.nereids.trees.expressions.literal.Literal;
import org.apache.doris.nereids.trees.expressions.literal.VarcharLiteral;
import org.apache.doris.nereids.trees.plans.JoinType;
import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.TableId;
import org.apache.doris.nereids.trees.plans.algebra.CatalogRelation;
import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier;
import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
@@ -213,8 +213,9 @@ public abstract class AbstractMaterializedViewRule
implements ExplorationRuleFac
MaterializationContext materializationContext) throws
AnalysisException {
List<Plan> rewriteResults = new ArrayList<>();
StructInfo viewStructInfo = materializationContext.getStructInfo();
- MatchMode matchMode = decideMatchMode(queryStructInfo.getRelations(),
viewStructInfo.getRelations());
- if (MatchMode.COMPLETE != matchMode) {
+ MatchMode matchMode = decideMatchMode(queryStructInfo.getRelations(),
viewStructInfo.getRelations(),
+ cascadesContext);
+ if (MatchMode.COMPLETE != matchMode && MatchMode.QUERY_PARTIAL !=
matchMode) {
materializationContext.recordFailReason(queryStructInfo, "Match
mode is invalid",
() -> String.format("matchMode is %s", matchMode));
return rewriteResults;
@@ -566,12 +567,12 @@ public abstract class AbstractMaterializedViewRule
implements ExplorationRuleFac
Map<Expression, ExpressionInfo> queryExprToInfoMap,
CascadesContext cascadesContext) {
// Firstly, rewrite the target expression using source with inverse
mapping
// then try to use the target expression to represent the query. if
any of source expressions
- // can not be represented by target expressions, return null.
+ // could not be represented by target expressions, return null.
// generate target to target replacement expression mapping, and
change target expression to source based
List<? extends Expression> sourceShuttledExpressions =
ExpressionUtils.shuttleExpressionWithLineage(
sourceExpressionsToWrite, sourcePlan, sourcePlanBitSet);
ExpressionMapping expressionMappingKeySourceBased =
targetExpressionMapping.keyPermute(targetToSourceMapping);
- // target to target replacement expression mapping, because mv is 1:1
so get first element
+ // target to target replacement expression mapping, because mv is 1:1
so get the first element
List<Map<Expression, Expression>> flattenExpressionMap =
expressionMappingKeySourceBased.flattenMap();
Map<Expression, Expression> targetToTargetReplacementMappingQueryBased
=
flattenExpressionMap.get(0);
@@ -602,7 +603,7 @@ public abstract class AbstractMaterializedViewRule
implements ExplorationRuleFac
targetToTargetReplacementMappingQueryBased);
Set<Expression> replacedExpressionSlotQueryUsed =
replacedExpression.collect(slotsToRewrite::contains);
if (!replacedExpressionSlotQueryUsed.isEmpty()) {
- // if contains any slot to rewrite, which means can not be
rewritten by target,
+ // if contains any slot to rewrite, which means could not be
rewritten by target,
// expressionShuttledToRewrite is slot#0 > '2024-01-01' but mv
plan output is date_trunc(slot#0, 'day')
// which would try to rewrite
if (viewExprParamToDateTruncMap.isEmpty()
@@ -617,7 +618,7 @@ public abstract class AbstractMaterializedViewRule
implements ExplorationRuleFac
if (!queryExprToInfoMap.containsKey(queryOriginalExpr)
||
!viewExprParamToDateTruncMap.containsKey(queryShuttledExprParam)) {
// query expr contains expression info or mv out contains
date_trunc expression,
- // if not, can not try to rewritten by view date_trunc,
bail out
+ // if not, could not try to be rewritten by view
date_trunc, bail out
return ImmutableList.of();
}
Map<Expression, Expression> datetruncMap = new HashMap<>();
@@ -650,7 +651,7 @@ public abstract class AbstractMaterializedViewRule
implements ExplorationRuleFac
}
/**
- * if query contains variant slot reference, extend the expression mapping
for rewrte
+ * if query contains variant slot reference, extend the expression mapping
for rewrite
* such as targetToTargetReplacementMappingQueryBased is
* id#0 -> id#8
* type#1 -> type#9
@@ -912,15 +913,15 @@ public abstract class AbstractMaterializedViewRule
implements ExplorationRuleFac
*
* @see MatchMode
*/
- private MatchMode decideMatchMode(List<CatalogRelation> queryRelations,
List<CatalogRelation> viewRelations) {
-
- Set<TableIdentifier> queryTables = new HashSet<>();
+ private MatchMode decideMatchMode(List<CatalogRelation> queryRelations,
List<CatalogRelation> viewRelations,
+ CascadesContext cascadesContext) {
+ Set<TableId> queryTables = new HashSet<>();
for (CatalogRelation catalogRelation : queryRelations) {
- queryTables.add(new TableIdentifier(catalogRelation.getTable()));
+
queryTables.add(cascadesContext.getStatementContext().getTableId(catalogRelation.getTable()));
}
- Set<TableIdentifier> viewTables = new HashSet<>();
+ Set<TableId> viewTables = new HashSet<>();
for (CatalogRelation catalogRelation : viewRelations) {
- viewTables.add(new TableIdentifier(catalogRelation.getTable()));
+
viewTables.add(cascadesContext.getStatementContext().getTableId(catalogRelation.getTable()));
}
if (queryTables.equals(viewTables)) {
return MatchMode.COMPLETE;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewScanRule.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewScanRule.java
index 1fc899f5993..a91e2314d32 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewScanRule.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewScanRule.java
@@ -85,4 +85,11 @@ public abstract class AbstractMaterializedViewScanRule
extends AbstractMateriali
&& !checkContext.isContainsTopLimit() &&
!checkContext.isContainsTopTopN()
&& !checkContext.isContainsTopWindow();
}
+
+ @Override
+ protected boolean checkMaterializationPattern(StructInfo structInfo,
CascadesContext cascadesContext) {
+ PlanCheckContext checkContext =
PlanCheckContext.of(SUPPORTED_JOIN_TYPE_SET);
+ return structInfo.getTopPlan().accept(StructInfo.PLAN_PATTERN_CHECKER,
checkContext)
+ && !checkContext.isContainsTopAggregate();
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/HyperGraphComparator.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/HyperGraphComparator.java
index f6d23e6a08a..8a6ed611cd1 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/HyperGraphComparator.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/HyperGraphComparator.java
@@ -88,7 +88,8 @@ public class HyperGraphComparator {
private final Map<JoinEdge, Pair<JoinType, Pair<Set<Slot>, Set<Slot>>>>
inferredViewEdgeWithCond = new HashMap<>();
private List<JoinEdge> viewJoinEdgesAfterInferring;
private List<FilterEdge> viewFilterEdgesAfterInferring;
- private final long eliminateViewNodesMap;
+ private final long shouldEliminateViewNodesMap;
+ private long reservedShouldEliminatedViewNodes;
/**
* constructor
@@ -98,9 +99,10 @@ public class HyperGraphComparator {
this.queryHyperGraph = queryHyperGraph;
this.viewHyperGraph = viewHyperGraph;
this.logicalCompatibilityContext = logicalCompatibilityContext;
- this.eliminateViewNodesMap = LongBitmap.newBitmapDiff(
+ this.shouldEliminateViewNodesMap = LongBitmap.newBitmapDiff(
viewHyperGraph.getNodesMap(),
LongBitmap.newBitmap(logicalCompatibilityContext.getQueryToViewNodeIDMapping().values()));
+ this.reservedShouldEliminatedViewNodes =
this.shouldEliminateViewNodesMap;
}
/**
@@ -156,11 +158,11 @@ public class HyperGraphComparator {
.forEach(e -> pullUpQueryExprWithEdge.put(e,
e.getExpressions()));
Sets.difference(getViewJoinEdgeSet(),
Sets.newHashSet(queryToViewJoinEdge.values()))
.stream()
- .filter(e -> !LongBitmap.isOverlap(e.getReferenceNodes(),
eliminateViewNodesMap))
+ .filter(e -> !LongBitmap.isOverlap(e.getReferenceNodes(),
shouldEliminateViewNodesMap))
.forEach(e -> pullUpViewExprWithEdge.put(e,
e.getExpressions()));
Sets.difference(getViewFilterEdgeSet(),
Sets.newHashSet(queryToViewFilterEdge.values()))
.stream()
- .filter(e -> !LongBitmap.isOverlap(e.getReferenceNodes(),
eliminateViewNodesMap))
+ .filter(e -> !LongBitmap.isOverlap(e.getReferenceNodes(),
shouldEliminateViewNodesMap))
.forEach(e -> pullUpViewExprWithEdge.put(e,
e.getExpressions()));
return buildComparisonRes();
@@ -196,7 +198,7 @@ public class HyperGraphComparator {
// eliminate by unique
if (joinEdge.getJoinType().isLeftOuterJoin() &&
joinEdge.isRightSimple()) {
long eliminatedRight =
-
LongBitmap.newBitmapIntersect(joinEdge.getRightExtendedNodes(),
eliminateViewNodesMap);
+
LongBitmap.newBitmapIntersect(joinEdge.getRightExtendedNodes(),
reservedShouldEliminatedViewNodes);
if (LongBitmap.getCardinality(eliminatedRight) != 1) {
return false;
}
@@ -204,8 +206,14 @@ public class HyperGraphComparator {
if (rigthPlan == null) {
return false;
}
- return JoinUtils.canEliminateByLeft(joinEdge.getJoin(),
+ boolean couldEliminateByLeft =
JoinUtils.canEliminateByLeft(joinEdge.getJoin(),
rigthPlan.getLogicalProperties().getTrait());
+ // if eliminated successfully, should refresh the
eliminateViewNodesMap
+ if (couldEliminateByLeft) {
+ this.reservedShouldEliminatedViewNodes =
+
LongBitmap.newBitmapDiff(reservedShouldEliminatedViewNodes, eliminatedRight);
+ }
+ return couldEliminateByLeft;
}
// eliminate by pk fk
if (joinEdge.getJoinType().isInnerJoin()) {
@@ -213,17 +221,30 @@ public class HyperGraphComparator {
return false;
}
long eliminatedLeft =
-
LongBitmap.newBitmapIntersect(joinEdge.getLeftExtendedNodes(),
eliminateViewNodesMap);
+
LongBitmap.newBitmapIntersect(joinEdge.getLeftExtendedNodes(),
reservedShouldEliminatedViewNodes);
long eliminatedRight =
-
LongBitmap.newBitmapIntersect(joinEdge.getRightExtendedNodes(),
eliminateViewNodesMap);
+
LongBitmap.newBitmapIntersect(joinEdge.getRightExtendedNodes(),
reservedShouldEliminatedViewNodes);
+ // Only eliminate the node which is in eliminateViewNodesMap
if (LongBitmap.getCardinality(eliminatedLeft) == 0
&& LongBitmap.getCardinality(eliminatedRight) == 1) {
- return
canEliminatePrimaryByForeign(joinEdge.getRightExtendedNodes(),
joinEdge.getLeftExtendedNodes(),
+ boolean canEliminated =
canEliminatePrimaryByForeign(joinEdge.getRightExtendedNodes(),
+ joinEdge.getLeftExtendedNodes(),
joinEdge.getRightInputSlots(),
joinEdge.getLeftInputSlots(), joinEdge);
+ if (canEliminated) {
+ this.reservedShouldEliminatedViewNodes =
LongBitmap.newBitmapDiff(
+ reservedShouldEliminatedViewNodes,
joinEdge.getRightExtendedNodes());
+ }
+ return canEliminated;
} else if (LongBitmap.getCardinality(eliminatedLeft) == 1
&& LongBitmap.getCardinality(eliminatedRight) == 0) {
- return
canEliminatePrimaryByForeign(joinEdge.getLeftExtendedNodes(),
joinEdge.getRightExtendedNodes(),
+ boolean canEliminate =
canEliminatePrimaryByForeign(joinEdge.getLeftExtendedNodes(),
+ joinEdge.getRightExtendedNodes(),
joinEdge.getLeftInputSlots(),
joinEdge.getRightInputSlots(), joinEdge);
+ if (canEliminate) {
+ this.reservedShouldEliminatedViewNodes =
LongBitmap.newBitmapDiff(
+ reservedShouldEliminatedViewNodes,
joinEdge.getLeftExtendedNodes());
+ }
+ return canEliminate;
}
}
return false;
@@ -232,15 +253,46 @@ public class HyperGraphComparator {
private boolean tryEliminateNodesAndEdge() {
boolean hasFilterEdgeAbove = viewHyperGraph.getFilterEdges().stream()
.filter(e -> LongBitmap.getCardinality(e.getReferenceNodes())
== 1)
- .anyMatch(e -> LongBitmap.isSubset(e.getReferenceNodes(),
eliminateViewNodesMap));
+ .anyMatch(e -> LongBitmap.isSubset(e.getReferenceNodes(),
shouldEliminateViewNodesMap));
if (hasFilterEdgeAbove) {
// If there is some filter edge above the eliminated node, we
should rebuild a plan
// Right now, just reject it.
return false;
}
- return viewHyperGraph.getJoinEdges().stream()
- .filter(joinEdge ->
LongBitmap.isOverlap(joinEdge.getReferenceNodes(), eliminateViewNodesMap))
- .allMatch(this::canEliminateViewEdge);
+ long allCanEliminateNodes = 0;
+ for (JoinEdge joinEdge : viewHyperGraph.getJoinEdges()) {
+ long canEliminateSideNodes = getCanEliminateSideNodes(joinEdge);
+ allCanEliminateNodes = LongBitmap.or(allCanEliminateNodes,
canEliminateSideNodes);
+ if (LongBitmap.isOverlap(canEliminateSideNodes,
reservedShouldEliminatedViewNodes)
+ && !canEliminateViewEdge(joinEdge)) {
+ return false;
+ }
+ }
+ // check all can eliminateNodes contains all should eliminate nodes,
to avoid some nodes can not be eliminated
+ // but in shouldEliminateViewNodesMap
+ // check all needed to eliminate nodes already be eliminated
+ return LongBitmap.containAll(allCanEliminateNodes,
shouldEliminateViewNodesMap)
+ &&
LongBitmap.getCardinality(reservedShouldEliminatedViewNodes) == 0;
+ }
+
+ private static long getCanEliminateSideNodes(JoinEdge joinEdge) {
+ long leftExtendedNodes = joinEdge.getLeftExtendedNodes();
+ long rightExtendedNodes = joinEdge.getRightExtendedNodes();
+ long nodesToCheck = LongBitmap.newBitmap();
+ if (joinEdge.getJoinType().isLeftOuterJoin() &&
joinEdge.isRightSimple()) {
+ if (LongBitmap.getCardinality(rightExtendedNodes) == 1) {
+ nodesToCheck = LongBitmap.or(rightExtendedNodes, nodesToCheck);
+ }
+ }
+ if (joinEdge.getJoinType().isInnerJoin()) {
+ if (LongBitmap.getCardinality(leftExtendedNodes) == 1) {
+ nodesToCheck = LongBitmap.or(leftExtendedNodes, nodesToCheck);
+ }
+ if (LongBitmap.getCardinality(rightExtendedNodes) == 1) {
+ nodesToCheck = LongBitmap.or(rightExtendedNodes, nodesToCheck);
+ }
+ }
+ return nodesToCheck;
}
private boolean compareNodeWithExpr(StructInfoNode query, StructInfoNode
view) {
@@ -558,12 +610,17 @@ public class HyperGraphComparator {
private boolean compareJoinEdgeWithNode(JoinEdge query, JoinEdge view) {
boolean res = false;
+ // if eliminateViewNodesMap is not empty, we should compare the join
nodes after eliminating
if (query.getJoinType().swap() == view.getJoinType()) {
- res |= getViewNodesByQuery(query.getLeftExtendedNodes()) ==
view.getRightExtendedNodes()
- && getViewNodesByQuery(query.getRightExtendedNodes()) ==
view.getLeftExtendedNodes();
- }
- res |= getViewNodesByQuery(query.getLeftExtendedNodes()) ==
view.getLeftExtendedNodes()
- && getViewNodesByQuery(query.getRightExtendedNodes()) ==
view.getRightExtendedNodes();
+ res |= getViewNodesByQuery(query.getLeftExtendedNodes()) ==
LongBitmap.newBitmapDiff(
+ view.getRightExtendedNodes(),
this.shouldEliminateViewNodesMap)
+ && getViewNodesByQuery(query.getRightExtendedNodes()) ==
LongBitmap.newBitmapDiff(
+ view.getLeftExtendedNodes(),
this.shouldEliminateViewNodesMap);
+ }
+ res |= getViewNodesByQuery(query.getLeftExtendedNodes()) ==
LongBitmap.newBitmapDiff(
+ view.getLeftExtendedNodes(), this.shouldEliminateViewNodesMap)
+ && getViewNodesByQuery(query.getRightExtendedNodes()) ==
LongBitmap.newBitmapDiff(
+ view.getRightExtendedNodes(),
this.shouldEliminateViewNodesMap);
return res;
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
index 3341c499904..347b800c096 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
@@ -286,12 +286,11 @@ public class MaterializedViewUtils {
ImmutableList.Builder<StructInfo> structInfosBuilder =
ImmutableList.builder();
if (!queryTableSets.isEmpty()) {
for (BitSet queryTableSet : queryTableSets) {
- // TODO As only support MatchMode.COMPLETE, so only get
equaled query table struct info
BitSet queryCommonTableSet =
MaterializedViewUtils.transformToCommonTableId(queryTableSet,
cascadesContext.getStatementContext().getRelationIdToCommonTableIdMap());
// compare relation id corresponding table id
if (!materializedViewTableSet.isEmpty()
- &&
!materializedViewTableSet.equals(queryCommonTableSet)) {
+ && !containsAll(materializedViewTableSet,
queryCommonTableSet)) {
continue;
}
StructInfo structInfo =
structInfoMap.getStructInfo(cascadesContext, queryTableSet, ownerGroup,
@@ -586,6 +585,22 @@ public class MaterializedViewUtils {
return Pair.of(chosenMaterializationMap, usedRelation);
}
+ /**
+ * Checks if the superset contains all of the set bits from the subset.
+ *
+ * @param superset The BitSet expected to contain the bits.
+ * @param subset The BitSet whose set bits are to be checked.
+ * @return true if all bits set in the subset are also set in the
superset, false otherwise.
+ */
+ public static boolean containsAll(BitSet superset, BitSet subset) {
+ // Clone the subset to avoid modifying the original instance.
+ BitSet temp = (BitSet) subset.clone();
+ // Remove all bits from temp that are also present in the superset.
+ // temp.andNot(superset) is equivalent to the operation: temp = temp
AND (NOT superset)
+ temp.andNot(superset);
+ return temp.isEmpty();
+ }
+
/**
* Check the query if Contains query operator
* Such sql as following should return true
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/ExpressionMapping.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/ExpressionMapping.java
index 8c77eacfaf0..98cf50a578f 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/ExpressionMapping.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/ExpressionMapping.java
@@ -19,6 +19,7 @@ package org.apache.doris.nereids.rules.exploration.mv.mapping;
import org.apache.doris.common.Pair;
import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
import org.apache.doris.nereids.util.ExpressionUtils;
import org.apache.doris.nereids.util.Utils;
@@ -82,7 +83,14 @@ public class ExpressionMapping extends Mapping {
this.getExpressionMapping().asMap();
for (Map.Entry<? extends Expression, ? extends Collection<? extends
Expression>> entry :
expressionMap.entrySet()) {
- Expression replacedExpr = ExpressionUtils.replace(entry.getKey(),
slotMapping.toSlotReferenceMap());
+ // permute by slot mapping, if entry expression is not in slot
mapping should discard,
+ // because in view partial rewrite, mv plan output is more than
query output, so should discard
+ // the expression not in slot mapping
+ Map<SlotReference, SlotReference> slotReferenceMap =
slotMapping.toSlotReferenceMap();
+ Expression replacedExpr =
ExpressionUtils.replaceNullAware(entry.getKey(), slotReferenceMap);
+ if (replacedExpr == null) {
+ continue;
+ }
permutedExpressionMapping.putAll(replacedExpr, entry.getValue());
}
return new ExpressionMapping(permutedExpressionMapping);
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
index 6e481a10142..68aa6aba4a1 100644
---
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
@@ -87,7 +87,7 @@ public class SlotMapping extends Mapping {
// payload['issue']['number']#20 ->
element_at(element_at(payload#10, 'issue'), 'number') mapping
// in expression rewrite
// 2
- // Maybe table add column after last refresh
+ // Maybe table added column after last refresh
LOG.warn(String.format("SlotMapping generate is null,
source relation is %s, "
+ "target relation is %s", sourceRelation,
targetRelation));
continue;
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
index 51e2308cbbf..346e748f390 100644
---
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
@@ -84,6 +84,13 @@ public class ExpressionLineageReplacer extends
DefaultPlanVisitor<Expression, Ex
public Expression visitNamedExpression(NamedExpression namedExpression,
Map<ExprId, Expression> exprIdExpressionMap) {
Expression childExpr =
exprIdExpressionMap.get(namedExpression.getExprId());
+ // avoid loop when non_nullable(o_orderkey#0) AS `o_orderkey`#0
after join eliminate when
+ // inner join
+ if (childExpr != null && !childExpr.children().isEmpty()
+ && childExpr.child(0) instanceof NamedExpression
+ && ((NamedExpression)
childExpr.child(0)).getExprId().equals(namedExpression.getExprId())) {
+ return namedExpression;
+ }
if (childExpr != null) {
// remove alias
return visit(childExpr, exprIdExpressionMap);
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 cd04627ee30..34afc3652dc 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
@@ -70,6 +70,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation;
import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
import org.apache.doris.nereids.trees.plans.visitor.ExpressionLineageReplacer;
import org.apache.doris.nereids.types.BooleanType;
+import org.apache.doris.nereids.types.VariantType;
import org.apache.doris.nereids.types.coercion.NumericType;
import org.apache.doris.qe.ConnectContext;
@@ -540,6 +541,32 @@ public class ExpressionUtils {
return result.build();
}
+ /**
+ * replaceNullAware, if could not be replaced by map, the return null
+ */
+ public static Expression replaceNullAware(Expression expr,
+ Map<? extends Expression, ? extends Expression> replaceMap) {
+ Set<Boolean> containNull = new HashSet<>();
+ Expression finalReplacedExpr = expr.rewriteDownShortCircuit(e -> {
+ if (!containNull.isEmpty()) {
+ return e;
+ }
+ Expression replacedExpr = replaceMap.get(e);
+ if (replacedExpr == null && e instanceof SlotReference
+ && e.getDataType() instanceof VariantType) {
+ // this is valid, because the variant expression would be
extended in expression rewrite
+ return e;
+ }
+ if (replacedExpr == null && e instanceof NamedExpression) {
+ // if replace named expression failed, return null directly
+ containNull.add(true);
+ return e;
+ }
+ return replacedExpr == null ? e : replacedExpr;
+ });
+ return containNull.isEmpty() ? finalReplacedExpr : null;
+ }
+
/**
* Replace expression node in the expression tree by `replaceMap` in
top-down manner.
*/
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/BuildStructInfoTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/BuildStructInfoTest.java
index c1168224829..bfecdbd4181 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/BuildStructInfoTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/BuildStructInfoTest.java
@@ -18,14 +18,17 @@
package org.apache.doris.nereids.rules.exploration.mv;
import org.apache.doris.nereids.jobs.joinorder.hypergraph.HyperGraph;
+import
org.apache.doris.nereids.rules.exploration.mv.StructInfo.PlanCheckContext;
import org.apache.doris.nereids.sqltest.SqlTestBase;
import org.apache.doris.nereids.trees.plans.GroupPlan;
+import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.util.PlanChecker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class BuildStructInfoTest extends SqlTestBase {
+
@Test
void testSimpleSQL() {
connectContext.getSessionVariable().setDisableNereidsRules("PRUNE_EMPTY_PARTITION");
@@ -97,4 +100,108 @@ class BuildStructInfoTest extends SqlTestBase {
return true;
}));
}
+
+ @Test
+ void testPlanCheckerWithJoin() {
+ // Should not make scan to empty relation when the table used by
materialized view has no data
+ connectContext.getSessionVariable().setDisableNereidsRules(
+ "OLAP_SCAN_PARTITION_PRUNE"
+ + ",PRUNE_EMPTY_PARTITION"
+ + ",ELIMINATE_GROUP_BY_KEY_BY_UNIFORM"
+ + ",ELIMINATE_CONST_JOIN_CONDITION"
+ + ",CONSTANT_PROPAGATION"
+ );
+ PlanChecker.from(connectContext)
+ .checkExplain("select * from "
+ + "(select * from lineitem "
+ + "where l_shipdate >= \"2023-12-01\" and
l_shipdate <= \"2023-12-03\") t1 "
+ + "left join "
+ + "(select * from orders where o_orderdate >=
\"2023-12-01\" and o_orderdate <= \"2023-12-03\" ) t2 "
+ + "on t1.l_orderkey = o_orderkey;",
+ nereidsPlanner -> {
+ Plan rewrittenPlan =
nereidsPlanner.getRewrittenPlan();
+ PlanCheckContext checkContext =
PlanCheckContext.of(
+
AbstractMaterializedViewRule.SUPPORTED_JOIN_TYPE_SET);
+ Boolean result =
rewrittenPlan.child(0).accept(StructInfo.PLAN_PATTERN_CHECKER, checkContext);
+ Assertions.assertTrue(result);
+
Assertions.assertFalse(checkContext.isContainsTopAggregate());
+ });
+ }
+
+ @Test
+ void testPlanCheckerWithAggregate() {
+ // Should not make scan to empty relation when the table used by
materialized view has no data
+ connectContext.getSessionVariable().setDisableNereidsRules(
+ "OLAP_SCAN_PARTITION_PRUNE"
+ + ",PRUNE_EMPTY_PARTITION"
+ + ",ELIMINATE_GROUP_BY_KEY_BY_UNIFORM"
+ + ",ELIMINATE_CONST_JOIN_CONDITION"
+ + ",CONSTANT_PROPAGATION"
+ );
+ PlanChecker.from(connectContext)
+ .checkExplain("SELECT l.L_SHIPDATE AS ship_data_alias,
o.O_ORDERDATE, count(*) "
+ + "FROM "
+ + "lineitem as l "
+ + "LEFT JOIN "
+ + "(SELECT abs(O_TOTALPRICE + 10) as c1_abs,
O_CUSTKEY, O_ORDERDATE, 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 "
+ + "GROUP BY l.L_SHIPDATE, o.O_ORDERDATE ",
+ nereidsPlanner -> {
+ Plan rewrittenPlan =
nereidsPlanner.getRewrittenPlan();
+ PlanCheckContext checkContext =
PlanCheckContext.of(
+
AbstractMaterializedViewRule.SUPPORTED_JOIN_TYPE_SET);
+ Boolean result =
rewrittenPlan.child(0).accept(StructInfo.PLAN_PATTERN_CHECKER, checkContext);
+ Assertions.assertTrue(result);
+
Assertions.assertTrue(checkContext.isContainsTopAggregate());
+ });
+ }
+
+ @Test
+ void testPlanCheckerScanAggregate() {
+ // Should not make scan to empty relation when the table used by
materialized view has no data
+ connectContext.getSessionVariable().setDisableNereidsRules(
+ "OLAP_SCAN_PARTITION_PRUNE"
+ + ",PRUNE_EMPTY_PARTITION"
+ + ",ELIMINATE_GROUP_BY_KEY_BY_UNIFORM"
+ + ",ELIMINATE_CONST_JOIN_CONDITION"
+ + ",CONSTANT_PROPAGATION"
+ );
+ PlanChecker.from(connectContext)
+ .checkExplain("select l.L_SHIPDATE, count(*) from lineitem l "
+ + "GROUP BY l.L_SHIPDATE",
+ nereidsPlanner -> {
+ Plan rewrittenPlan =
nereidsPlanner.getRewrittenPlan();
+ PlanCheckContext checkContext =
PlanCheckContext.of(
+
AbstractMaterializedViewRule.SUPPORTED_JOIN_TYPE_SET);
+ Boolean result =
rewrittenPlan.child(0).accept(StructInfo.SCAN_PLAN_PATTERN_CHECKER,
checkContext);
+ Assertions.assertFalse(result);
+ });
+ }
+
+ @Test
+ void testPlanCheckerOnlyScan() {
+ // Should not make scan to empty relation when the table used by
materialized view has no data
+ connectContext.getSessionVariable().setDisableNereidsRules(
+ "OLAP_SCAN_PARTITION_PRUNE"
+ + ",PRUNE_EMPTY_PARTITION"
+ + ",ELIMINATE_GROUP_BY_KEY_BY_UNIFORM"
+ + ",ELIMINATE_CONST_JOIN_CONDITION"
+ + ",CONSTANT_PROPAGATION"
+ );
+ PlanChecker.from(connectContext)
+ .checkExplain("select l.L_SHIPDATE from lineitem l ",
+ nereidsPlanner -> {
+ Plan rewrittenPlan =
nereidsPlanner.getRewrittenPlan();
+ PlanCheckContext checkContext =
PlanCheckContext.of(
+
AbstractMaterializedViewRule.SUPPORTED_JOIN_TYPE_SET);
+ Boolean result =
rewrittenPlan.child(0).accept(StructInfo.SCAN_PLAN_PATTERN_CHECKER,
checkContext);
+ Assertions.assertTrue(result);
+
Assertions.assertFalse(checkContext.isContainsTopAggregate());
+ });
+ }
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/HyperGraphComparatorTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/HyperGraphComparatorTest.java
index 884d9a15d81..db02e45dd48 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/HyperGraphComparatorTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/HyperGraphComparatorTest.java
@@ -25,7 +25,6 @@ import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.util.PlanChecker;
import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class HyperGraphComparatorTest extends SqlTestBase {
@@ -61,7 +60,7 @@ class HyperGraphComparatorTest extends SqlTestBase {
HyperGraph h1 = HyperGraph.builderForMv(p1).build();
HyperGraph h2 = HyperGraph.builderForMv(p2).build();
ComparisonResult res = HyperGraphComparator.isLogicCompatible(h1, h2,
constructContext(p1, p2, c1));
- Assertions.assertTrue(!res.isInvalid());
+ Assertions.assertFalse(res.isInvalid());
Assertions.assertEquals(2, res.getViewNoNullableSlot().size());
}
@@ -91,7 +90,7 @@ class HyperGraphComparatorTest extends SqlTestBase {
HyperGraph h1 = HyperGraph.builderForMv(p1).build();
HyperGraph h2 = HyperGraph.builderForMv(p2).build();
ComparisonResult res = HyperGraphComparator.isLogicCompatible(h1, h2,
constructContext(p1, p2, c1));
- Assertions.assertTrue(!res.isInvalid());
+ Assertions.assertFalse(res.isInvalid());
Assertions.assertEquals(2, res.getViewNoNullableSlot().size());
}
@@ -122,11 +121,10 @@ class HyperGraphComparatorTest extends SqlTestBase {
HyperGraph h1 = HyperGraph.builderForMv(p1).build();
HyperGraph h2 = HyperGraph.builderForMv(p2).build();
ComparisonResult res = HyperGraphComparator.isLogicCompatible(h1, h2,
constructContext(p1, p2, c1));
- Assertions.assertTrue(!res.isInvalid());
+ Assertions.assertFalse(res.isInvalid());
Assertions.assertEquals(2, res.getViewNoNullableSlot().size());
}
- @Disabled
@Test
void testIJAndLojAssocWithJoinCond() {
CascadesContext c1 = createCascadesContext(
@@ -140,12 +138,9 @@ class HyperGraphComparatorTest extends SqlTestBase {
.rewrite()
.getPlan().child(0);
CascadesContext c2 = createCascadesContext(
- "select * from T1 left outer join "
- + "("
- + "select T1.* from T1 left outer join T3 "
- + "on T1.id = T3.id and T1.score = T3.score "
- + ") T2 "
- + "on T1.id = T2.id ",
+ "select * from T1 left join T2 "
+ + "on T1.id = T2.id "
+ + "left join T3 on T1.id = T3.id",
connectContext
);
Plan p2 = PlanChecker.from(c2)
@@ -156,7 +151,35 @@ class HyperGraphComparatorTest extends SqlTestBase {
HyperGraph h1 = HyperGraph.builderForMv(p1).build();
HyperGraph h2 = HyperGraph.builderForMv(p2).build();
ComparisonResult res = HyperGraphComparator.isLogicCompatible(h1, h2,
constructContext(p1, p2, c1));
- Assertions.assertTrue(!res.isInvalid());
+ Assertions.assertFalse(res.isInvalid());
Assertions.assertEquals(2, res.getViewNoNullableSlot().size());
}
+
+ @Test
+ void testJoinEliminateShouldFail() {
+ CascadesContext c1 = createCascadesContext(
+ "select * from T1 inner join T2 "
+ + "on T1.id = T2.id",
+ connectContext
+ );
+ Plan p1 = PlanChecker.from(c1)
+ .analyze()
+ .rewrite()
+ .getPlan().child(0);
+ CascadesContext c2 = createCascadesContext(
+ "select * from T1 inner join T2 "
+ + "on T1.id = T2.id "
+ + "inner join T3 on T1.id = T3.id",
+ connectContext
+ );
+ Plan p2 = PlanChecker.from(c2)
+ .analyze()
+ .rewrite()
+ .applyExploration(RuleSet.BUSHY_TREE_JOIN_REORDER)
+ .getAllPlan().get(0).child(0);
+ HyperGraph h1 = HyperGraph.builderForMv(p1).build();
+ HyperGraph h2 = HyperGraph.builderForMv(p2).build();
+ ComparisonResult res = HyperGraphComparator.isLogicCompatible(h1, h2,
constructContext(p1, p2, c1));
+ Assertions.assertTrue(res.isInvalid());
+ }
}
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 81611252ff9..1e952255891 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
@@ -17,22 +17,32 @@
package org.apache.doris.nereids.util;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.TableTest;
import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.expressions.Alias;
import org.apache.doris.nereids.trees.expressions.And;
import org.apache.doris.nereids.trees.expressions.EqualTo;
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 org.apache.doris.nereids.trees.expressions.functions.scalar.NonNullable;
import org.apache.doris.nereids.trees.expressions.literal.Literal;
import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.RelationId;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOdbcScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.types.IntegerType;
import org.apache.doris.utframe.TestWithFeService;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -207,6 +217,42 @@ public class ExpressionUtilsTest extends TestWithFeService
{
Assertions.assertEquals(expectUniformSlots,
ExpressionUtils.extractUniformSlot(expression));
}
+ @Test
+ public void testSlotInputEqualsOutput() {
+ OlapTable olapTable = TableTest.newOlapTable(10000, "test", 0);
+ Slot a = new SlotReference("id", IntegerType.INSTANCE);
+ Slot b = new SlotReference("id", IntegerType.INSTANCE);
+ Alias bAlias = new Alias(b.getExprId(), new NonNullable(b));
+ LogicalProject<LogicalOdbcScan> project = new
LogicalProject<>(ImmutableList.of(a, bAlias),
+ new LogicalOdbcScan(new RelationId(0), olapTable,
ImmutableList.of("test")));
+ List<? extends Expression> expressions =
ExpressionUtils.shuttleExpressionWithLineage(project.getOutput(),
+ project, new BitSet());
+ // should not loop, should break out loop
+ Assertions.assertEquals(expressions, ImmutableList.of(a,
bAlias.toSlot()));
+ }
+
+ @Test
+ public void testReplaceNullAware() {
+ Slot a = new SlotReference("id1", IntegerType.INSTANCE);
+ Slot b = new SlotReference("id2", IntegerType.INSTANCE);
+
+ Map<Expression, Expression> replaceMap = new HashMap<>();
+ replaceMap.put(a, b);
+ Expression replacedExpression = ExpressionUtils.replaceNullAware(a,
replaceMap);
+ Assertions.assertEquals(replacedExpression, b);
+
+ replaceMap = new HashMap<>();
+ Slot a2 = new SlotReference("id3", IntegerType.INSTANCE);
+ replaceMap.put(a2, b);
+ Expression replacedExpression1 = ExpressionUtils.replaceNullAware(a,
replaceMap);
+ // should return null
+ Assertions.assertNull(replacedExpression1);
+
+ Expression replacedExpression2 = ExpressionUtils.replace(a,
replaceMap);
+ // should return a
+ Assertions.assertEquals(a, replacedExpression2);
+ }
+
private void assertExpect(List<? extends Expression> originalExpressions,
List<? extends Expression> shuttledExpressions,
String... expectExpressions) {
diff --git
a/regression-test/suites/nereids_rules_p0/mv/join_elim_p_f_key/join_elim_line_pattern.groovy
b/regression-test/suites/nereids_rules_p0/mv/join_elim_p_f_key/join_elim_line_pattern.groovy
new file mode 100644
index 00000000000..9e1618d18dd
--- /dev/null
+++
b/regression-test/suites/nereids_rules_p0/mv/join_elim_p_f_key/join_elim_line_pattern.groovy
@@ -0,0 +1,558 @@
+package mv.join_elim_p_f_key
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("join_elim_line_pattern") {
+ String db = context.config.getDbNameByFile(context.file)
+ sql "use ${db}"
+ sql "SET enable_nereids_planner=true"
+ sql "SET enable_fallback_to_original_planner=false"
+ sql "SET enable_materialized_view_rewrite=true"
+ sql "SET enable_nereids_timeout = false"
+
+ sql """
+ drop table if exists orders
+ """
+
+ sql """CREATE TABLE `orders` (
+ `o_orderkey` BIGINT NULL,
+ `o_partkey` INT NULL,
+ `o_suppkey` INT NULL,
+ `o_custkey` INT NULL,
+ `o_orderstatus` VARCHAR(1) NULL,
+ `o_totalprice` DECIMAL(15, 2) NULL,
+ `o_orderpriority` VARCHAR(15) NULL,
+ `o_clerk` VARCHAR(15) NULL,
+ `o_shippriority` INT NULL,
+ `o_comment` VARCHAR(79) NULL,
+ `o_orderdate` DATE not NULL
+ ) ENGINE=OLAP
+ DUPLICATE KEY(`o_orderkey`)
+ COMMENT 'OLAP'
+ auto partition by range (date_trunc(`o_orderdate`, 'day')) ()
+ DISTRIBUTED BY HASH(`o_orderkey`) BUCKETS 96
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ );"""
+
+ sql """
+ drop table if exists lineitem
+ """
+
+ sql """CREATE TABLE `lineitem` (
+ `l_orderkey` BIGINT NOT NULL,
+ `l_linenumber` INT NULL,
+ `l_partkey` INT NOT NULL,
+ `l_suppkey` INT NOT NULL,
+ `l_quantity` DECIMAL(15, 2) NULL,
+ `l_extendedprice` DECIMAL(15, 2) NULL,
+ `l_discount` DECIMAL(15, 2) NULL,
+ `l_tax` DECIMAL(15, 2) NULL,
+ `l_returnflag` VARCHAR(1) NULL,
+ `l_linestatus` VARCHAR(1) NULL,
+ `l_commitdate` DATE NULL,
+ `l_receiptdate` DATE NULL,
+ `l_shipinstruct` VARCHAR(25) NULL,
+ `l_shipmode` VARCHAR(10) NULL,
+ `l_comment` VARCHAR(44) NULL,
+ `l_shipdate` DATE not NULL
+ ) ENGINE=OLAP
+ DUPLICATE KEY(l_orderkey)
+ COMMENT 'OLAP'
+ auto partition by range (date_trunc(`l_shipdate`, 'day')) ()
+ DISTRIBUTED BY HASH(`l_orderkey`) BUCKETS 96
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ );"""
+
+ sql """
+ drop table if exists partsupp
+ """
+
+ sql """CREATE TABLE `partsupp` (
+ `ps_partkey` INT NULL,
+ `ps_suppkey` INT NULL,
+ `ps_availqty` INT NULL,
+ `ps_supplycost` DECIMAL(15, 2) NULL,
+ `ps_comment` VARCHAR(199) NULL
+ ) ENGINE=OLAP
+ DUPLICATE KEY(`ps_partkey`)
+ COMMENT 'OLAP'
+ DISTRIBUTED BY HASH(`ps_partkey`) BUCKETS 24
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ );"""
+
+
+ sql """
+ INSERT INTO orders (
+ o_orderkey, o_partkey, o_suppkey, o_custkey, o_orderstatus,
+ o_totalprice, o_orderpriority, o_clerk, o_shippriority, o_comment,
o_orderdate
+ ) VALUES
+ (1001, 501, 201, 3001, 'O', 1500.00, '1-URGENT', 'Clerk#001', 0, '紧急订单',
'2024-01-15'),
+ (1001, 501, 201, 3001, 'O', 1500.00, '1-URGENT', 'Clerk#001', 0, '紧急订单',
'2024-01-15'),
+ (1002, 502, 202, 3002, 'F', 2500.00, '2-HIGH', 'Clerk#002', 1, '普通订单',
'2024-02-20'),
+ (1002, 502, 202, 3002, 'F', 2500.00, '2-HIGH', 'Clerk#002', 1, '普通订单',
'2024-02-20'),
+ (1003, 503, 203, 3003, 'O', 1800.00, '3-MEDIUM', 'Clerk#003', 0, '中等优先级',
'2024-03-05'),
+ (1003, 503, 203, 3003, 'O', 1800.00, '3-MEDIUM', 'Clerk#003', 0, '中等优先级',
'2024-03-05'),
+ (1004, 504, 204, 3004, 'F', 3200.00, '4-NOT SPEC', 'Clerk#004', 1, '大额订单',
'2024-03-15'),
+ (1004, 504, 204, 3004, 'F', 3200.00, '4-NOT SPEC', 'Clerk#004', 1, '大额订单',
'2024-03-15'),
+ (1005, 505, 205, 3005, 'O', 950.00, '5-LOW', 'Clerk#005', 0, '小额订单',
'2024-04-01'),
+ (1005, 505, 205, 3005, 'O', 950.00, '5-LOW', 'Clerk#005', 0, '小额订单',
'2024-04-01');
+
+ INSERT INTO lineitem (
+ l_orderkey, l_linenumber, l_partkey, l_suppkey, l_quantity,
+ l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus,
+ l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment,
l_shipdate
+ ) VALUES
+ -- 订单1001的明细
+ (1001, 1, 501, 201, 5.00, 500.00, 0.05, 0.10, 'N', 'O', '2024-01-16',
'2024-01-20', 'DELIVER IN PERSON', 'TRUCK', '订单1001第一行', '2024-01-18'),
+ (1002, 1, 502, 202, 8.00, 800.00, 0.08, 0.12, 'N', 'O', '2024-02-21',
'2024-02-25', 'TAKE BACK RETURN', 'MAIL', '订单1002第一行', '2024-02-22'),
+ (1003, 1, 503, 203, 6.00, 600.00, 0.06, 0.12, 'N', 'O', '2024-03-06',
'2024-03-10', 'DELIVER IN PERSON', 'TRUCK', '订单1003第一行', '2024-03-08'),
+ (1004, 1, 504, 204, 10.00, 1000.00, 0.10, 0.18, 'N', 'O', '2024-03-16',
'2024-03-20', 'TAKE BACK RETURN', 'MAIL', '订单1004第一行', '2024-03-18'),
+ (1005, 1, 505, 205, 4.00, 400.00, 0.04, 0.08, 'N', 'O', '2024-04-02',
'2024-04-06', 'DELIVER IN PERSON', 'TRUCK', '订单1005第一行', '2024-04-04');
+
+
+ INSERT INTO partsupp (
+ ps_partkey, ps_suppkey, ps_availqty, ps_supplycost, ps_comment
+ ) VALUES
+ (501, 201, 100, 50.00, '零件501供应商201'),
+ (502, 202, 200, 75.00, '零件502供应商202'),
+ (503, 203, 150, 60.00, '零件503供应商203'),
+ (504, 204, 180, 65.00, '零件504供应商204'),
+ (505, 205, 120, 55.00, '零件505供应商205');
+ """
+
+ sql """analyze table lineitem with sync;"""
+ sql """analyze table orders with sync;"""
+ sql """analyze table partsupp with sync;"""
+ sql """alter table lineitem modify column l_comment set stats
('row_count'='10');"""
+ sql """alter table orders modify column o_comment set stats
('row_count'='5');"""
+ sql """alter table partsupp modify column ps_comment set stats
('row_count'='5');"""
+
+ def compare_res = { def stmt, int orderByColumns = 1 ->
+ sql "SET enable_materialized_view_rewrite=false"
+ def orderStmt = " order by " + (1..orderByColumns).join(", ")
+ def origin_res = sql stmt + orderStmt
+ logger.info("origin_res: " + origin_res)
+ sql "SET enable_materialized_view_rewrite=true"
+ def mv_origin_res = sql stmt + orderStmt
+ logger.info("mv_origin_res: " + mv_origin_res)
+ assertTrue((mv_origin_res == [] && origin_res == []) ||
(mv_origin_res.size() == origin_res.size()))
+ for (int row = 0; row < mv_origin_res.size(); row++) {
+ assertTrue(mv_origin_res[row].size() == origin_res[row].size())
+ for (int col = 0; col < mv_origin_res[row].size(); col++) {
+ assertTrue(mv_origin_res[row][col] == origin_res[row][col])
+ }
+ }
+ }
+
+ def dropAllConstraints = { def tableName ->
+ def getConstraintsQuery = "SHOW CONSTRAINTS FROM ${tableName}"
+ def constraints = sql getConstraintsQuery
+ logger.info("needed deleted constraints : ${constraints}")
+ constraints.each { constraint ->
+ def constraintName = constraint[0]
+ def dropConstraintSQL = "ALTER TABLE ${tableName} DROP CONSTRAINT
${constraintName}"
+ sql dropConstraintSQL
+ logger.info("delete ${tableName} constraits : ${constraintName}")
+ }
+ }
+
+ // inner join
+ def mv_stmt_1 = """
+ select o_orderkey, o_partkey, o_suppkey, o_custkey, o_comment,
+ l_partkey, l_suppkey, l_orderkey, l_commitdate, l_comment,
+ ps_suppkey, ps_partkey, ps_availqty, ps_supplycost, ps_comment
+ from orders o
+ inner join lineitem l on o_orderkey = l_orderkey
+ inner join partsupp p on l_partkey = ps_partkey and l_suppkey =
ps_suppkey
+ """
+ // outer join
+ def mv_stmt_2 = """
+ select o_orderkey, o_partkey, o_suppkey, o_custkey, o_comment,
+ l_partkey, l_suppkey, l_orderkey, l_commitdate, l_comment,
+ ps_suppkey, ps_partkey, ps_availqty, ps_supplycost, ps_comment
+ from orders o
+ left join lineitem l on o_orderkey = l_orderkey
+ left join partsupp p on l_partkey = ps_partkey and l_suppkey =
ps_suppkey
+ """
+
+ // single table
+ def query_1 = """select o_orderkey, o_partkey, o_suppkey, o_custkey,
o_comment from orders"""
+
+ // orders join lineitem, use both
+ def query_2 = """select o_orderkey, l_partkey, l_suppkey, l_orderkey,
l_commitdate from orders o join lineitem l on o_orderkey = l_orderkey"""
+ def query_3 = """select o_orderkey, l_partkey, l_suppkey, l_orderkey,
l_commitdate from orders o left join lineitem l on o_orderkey = l_orderkey"""
+
+ // orders join lineitem, use left
+ def query_4 = """select o_orderkey, o_partkey, o_suppkey, o_custkey,
o_comment from orders o join lineitem l on o_orderkey = l_orderkey"""
+ def query_5 = """select o_orderkey, o_partkey, o_suppkey, o_custkey,
o_comment from orders o left join lineitem l on o_orderkey = l_orderkey"""
+
+ // orders join lineitem, use right
+ def query_6 = """select l_partkey, l_suppkey, l_orderkey, l_commitdate,
l_comment from orders o join lineitem l on o_orderkey = l_orderkey"""
+ def query_7 = """select l_partkey, l_suppkey, l_orderkey, l_commitdate,
l_comment from orders o left join lineitem l on o_orderkey = l_orderkey"""
+
+ // lineitem join partsupp, use both
+ def query_8 = """select l_orderkey, l_linenumber, l_partkey, ps_suppkey,
ps_partkey from lineitem l join partsupp p on l_partkey = ps_partkey and
l_suppkey = ps_suppkey"""
+ def query_9 = """select l_orderkey, l_linenumber, l_partkey, ps_suppkey,
ps_partkey from lineitem l left join partsupp p on l_partkey = ps_partkey and
l_suppkey = ps_suppkey"""
+
+ // lineitem join partsupp, use left
+ def query_10 = """select l_partkey, l_suppkey, l_orderkey, l_commitdate,
l_comment from lineitem l join partsupp p on l_partkey = ps_partkey and
l_suppkey = ps_suppkey"""
+ def query_11 = """select l_partkey, l_suppkey, l_orderkey, l_commitdate,
l_comment from lineitem l left join partsupp p on l_partkey = ps_partkey and
l_suppkey = ps_suppkey"""
+
+ // lineitem join partsupp, use right
+ def query_12 = """select ps_suppkey, ps_partkey, ps_availqty,
ps_supplycost, ps_comment from lineitem l join partsupp p on l_partkey =
ps_partkey and l_suppkey = ps_suppkey"""
+ def query_13 = """select ps_suppkey, ps_partkey, ps_availqty,
ps_supplycost, ps_comment from lineitem l left join partsupp p on l_partkey =
ps_partkey and l_suppkey = ps_suppkey"""
+
+
+ def query_list = [query_1, query_2, query_3, query_4, query_5, query_6,
query_7, query_8, query_9,
+ query_10, query_11, query_12, query_13]
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+ // orders -> lineitem (fk-pk)
+ // lineitem -> partsupp (fk-pk)
+ sql """alter table partsupp add constraint pk primary key(ps_partkey,
ps_suppkey)"""
+ sql """alter table lineitem add constraint fk1 foreign key (l_partkey,
l_suppkey) references partsupp(ps_partkey, ps_suppkey)"""
+ sql """alter table lineitem add constraint pk primary key(l_orderkey)"""
+ sql """alter table orders add constraint fk foreign key (o_orderkey)
references lineitem(l_orderkey)"""
+
+ def mv_name = "join_elim_line_pattern"
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in [1, 2, 3, 4, 5, 6, 7]) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ }
+ else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [1, 2, 3, 4, 5, 6, 7]) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+ // orders -> lineitem (fk-pk)
+ sql """alter table lineitem add constraint pk primary key(l_orderkey)"""
+ sql """alter table orders add constraint fk foreign key (o_orderkey)
references lineitem(l_orderkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ }
+ else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+ // lineitem -> partsupp (fk-pk)
+ sql """alter table partsupp add constraint pk primary key(ps_partkey,
ps_suppkey)"""
+ sql """alter table lineitem add constraint fk1 foreign key (l_partkey,
l_suppkey) references partsupp(ps_partkey, ps_suppkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in [2, 4, 6]) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ }
+ else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [3, 5, 7]) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+ // orders -> lineitem (u)
+ // lineitem -> partsupp (fk-pk)
+ sql """alter table partsupp add constraint pk primary key (ps_partkey,
ps_suppkey)"""
+ sql """alter table lineitem add constraint fk1 foreign key (l_partkey,
l_suppkey) references partsupp(ps_partkey, ps_suppkey)"""
+ sql """alter table lineitem add constraint uk unique (l_orderkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in [2, 4, 6]) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ }
+ else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [1, 2, 3, 4, 5, 6, 7]) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+ // orders -> lineitem (fk-pk)
+ // lineitem -> partsupp (u)
+ sql """alter table partsupp add constraint pk unique (ps_partkey,
ps_suppkey)"""
+ sql """alter table lineitem add constraint pk primary key(l_orderkey)"""
+ sql """alter table orders add constraint fk foreign key (o_orderkey)
references lineitem(l_orderkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ }
+ else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [1, 2, 3, 4, 5, 6, 7]) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+ // orders -> lineitem (u)
+ // lineitem -> partsupp (u)
+ sql """alter table partsupp add constraint pk unique (ps_partkey,
ps_suppkey)"""
+ sql """alter table lineitem add constraint pk unique (l_orderkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ }
+ else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [1, 2, 3, 4, 5, 6, 7]) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+ // orders -> lineitem (u)
+ sql """alter table lineitem add constraint pk unique (l_orderkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ }
+ else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+ // lineitem -> partsupp (u)
+ sql """alter table partsupp add constraint pk unique (ps_partkey,
ps_suppkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ }
+ else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [3, 5, 7]) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+ // without constraints
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ }
+ else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success_without_check_chosen(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 5)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+ sql """alter table partsupp add constraint pk unique (ps_partkey,
ps_suppkey)"""
+ // negative examples orders
+ async_mv_rewrite_fail(db,
+ """
+ select l_shipdate, l_linenumber, o_orderkey, ps_suppkey from
orders
+ left join lineitem
+ on o_orderkey = l_orderkey
+ left join partsupp
+ on ps_partkey = l_partkey or ps_suppkey = l_suppkey
+ """,
+ """
+ select l_shipdate, l_linenumber, o_orderkey
+ from lineitem
+ left join orders on o_orderkey = l_orderkey
+ """,
+ "${mv_name}_neg_2")
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name}_neg_1;"""
+
+ dropAllConstraints("orders")
+ dropAllConstraints("lineitem")
+ dropAllConstraints("partsupp")
+
+ sql """
+ drop table if exists orders;
+ drop table if exists lineitem;
+ drop table if exists partsupp;
+ """
+}
diff --git
a/regression-test/suites/nereids_rules_p0/mv/join_elim_p_f_key/join_elim_star_pattern.groovy
b/regression-test/suites/nereids_rules_p0/mv/join_elim_p_f_key/join_elim_star_pattern.groovy
new file mode 100644
index 00000000000..d8850796a3e
--- /dev/null
+++
b/regression-test/suites/nereids_rules_p0/mv/join_elim_p_f_key/join_elim_star_pattern.groovy
@@ -0,0 +1,592 @@
+package mv.join_elim_p_f_key
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("join_elim_star_pattern") {
+ String db = context.config.getDbNameByFile(context.file)
+ sql "use ${db}"
+ sql "SET enable_nereids_planner=true"
+ sql "SET enable_fallback_to_original_planner=false"
+ sql "SET enable_materialized_view_rewrite=true"
+ sql "SET enable_nereids_timeout = false"
+
+ sql """
+ drop table if exists orders_1
+ """
+
+ sql """CREATE TABLE `orders_1` (
+ `o_orderkey` BIGINT NOT NULL,
+ `o_partkey` INT NOT NULL,
+ `o_suppkey` INT NOT NULL,
+ `o_custkey` INT NULL,
+ `o_orderstatus` VARCHAR(1) NULL,
+ `o_totalprice` DECIMAL(15, 2) NULL,
+ `o_orderpriority` VARCHAR(15) NULL,
+ `o_clerk` VARCHAR(15) NULL,
+ `o_shippriority` INT NULL,
+ `o_comment` VARCHAR(79) NULL,
+ `o_orderdate` DATE not NULL
+ ) ENGINE=OLAP
+ DUPLICATE KEY(`o_orderkey`)
+ COMMENT 'OLAP'
+ auto partition by range (date_trunc(`o_orderdate`, 'day')) ()
+ DISTRIBUTED BY HASH(`o_orderkey`) BUCKETS 96
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ );"""
+
+ sql """
+ drop table if exists lineitem_1
+ """
+
+ sql """CREATE TABLE `lineitem_1` (
+ `l_orderkey` BIGINT NOT NULL,
+ `l_linenumber` INT NULL,
+ `l_partkey` INT NOT NULL,
+ `l_suppkey` INT NOT NULL,
+ `l_quantity` DECIMAL(15, 2) NULL,
+ `l_extendedprice` DECIMAL(15, 2) NULL,
+ `l_discount` DECIMAL(15, 2) NULL,
+ `l_tax` DECIMAL(15, 2) NULL,
+ `l_returnflag` VARCHAR(1) NULL,
+ `l_linestatus` VARCHAR(1) NULL,
+ `l_commitdate` DATE NULL,
+ `l_receiptdate` DATE NULL,
+ `l_shipinstruct` VARCHAR(25) NULL,
+ `l_shipmode` VARCHAR(10) NULL,
+ `l_comment` VARCHAR(44) NULL,
+ `l_shipdate` DATE not NULL
+ ) ENGINE=OLAP
+ DUPLICATE KEY(l_orderkey)
+ COMMENT 'OLAP'
+ auto partition by range (date_trunc(`l_shipdate`, 'day')) ()
+ DISTRIBUTED BY HASH(`l_orderkey`) BUCKETS 96
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ );"""
+
+ sql """
+ drop table if exists partsupp_1
+ """
+
+ sql """CREATE TABLE `partsupp_1` (
+ `ps_partkey` INT NOT NULL,
+ `ps_suppkey` INT NOT NULL,
+ `ps_availqty` INT NULL,
+ `ps_supplycost` DECIMAL(15, 2) NULL,
+ `ps_comment` VARCHAR(199) NULL
+ ) ENGINE=OLAP
+ DUPLICATE KEY(`ps_partkey`)
+ COMMENT 'OLAP'
+ DISTRIBUTED BY HASH(`ps_partkey`) BUCKETS 24
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ );"""
+
+
+ sql """
+ INSERT INTO orders_1 (
+ o_orderkey, o_partkey, o_suppkey, o_custkey, o_orderstatus,
+ o_totalprice, o_orderpriority, o_clerk, o_shippriority, o_comment,
o_orderdate
+ ) VALUES
+ (1001, 501, 201, 3001, 'O', 1500.00, '1-URGENT', 'Clerk#001', 0, '紧急订单',
'2024-01-15'),
+ (1002, 502, 202, 3002, 'F', 2500.00, '2-HIGH', 'Clerk#002', 1, '普通订单',
'2024-02-20'),
+ (1003, 503, 203, 3003, 'O', 1800.00, '3-MEDIUM', 'Clerk#003', 0, '中等优先级',
'2024-03-05'),
+ (1004, 504, 204, 3004, 'F', 3200.00, '4-NOT SPEC', 'Clerk#004', 1, '大额订单',
'2024-03-15'),
+ (1005, 505, 205, 3005, 'O', 950.00, '5-LOW', 'Clerk#005', 0, '小额订单',
'2024-04-01'),
+ (1006, 506, 206, 3005, 'O', 950.00, '5-LOW', 'Clerk#005', 0, '小额订单',
'2024-04-01'),
+ (1007, 507, 207, 3005, 'O', 950.00, '5-LOW', 'Clerk#005', 0, '小额订单',
'2024-04-01'),
+ (1008, 508, 208, 3005, 'O', 950.00, '5-LOW', 'Clerk#005', 0, '小额订单',
'2024-04-01');
+
+ INSERT INTO lineitem_1 (
+ l_orderkey, l_linenumber, l_partkey, l_suppkey, l_quantity,
+ l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus,
+ l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment,
l_shipdate
+ ) VALUES
+ (1001, 1, 501, 201, 5.00, 500.00, 0.05, 0.10, 'N', 'O', '2024-01-16',
'2024-01-20', 'DELIVER IN PERSON', 'TRUCK', '订单1001第一行', '2024-01-18'),
+ (1001, 2, 502, 202, 10.00, 1000.00, 0.10, 0.15, 'N', 'O', '2024-01-16',
'2024-01-20', 'NONE', 'AIR', '订单1001第二行', '2024-01-18'),
+ (1002, 1, 503, 203, 8.00, 800.00, 0.08, 0.12, 'N', 'O', '2024-02-21',
'2024-02-25', 'TAKE BACK RETURN', 'MAIL', '订单1002第一行', '2024-02-22'),
+ (1002, 2, 504, 204, 12.00, 1200.00, 0.12, 0.18, 'N', 'O', '2024-02-21',
'2024-02-26', 'COLLECT COD', 'SHIP', '订单1002第二行', '2024-02-23'),
+ (1003, 1, 505, 205, 6.00, 600.00, 0.06, 0.12, 'N', 'O', '2024-03-06',
'2024-03-10', 'DELIVER IN PERSON', 'TRUCK', '订单1003第一行', '2024-03-08'),
+ (1003, 2, 506, 206, 9.00, 900.00, 0.09, 0.15, 'N', 'O', '2024-03-06',
'2024-03-11', 'NONE', 'AIR', '订单1003第二行', '2024-03-09'),
+ (1004, 1, 507, 207, 10.00, 1000.00, 0.10, 0.18, 'N', 'O', '2024-03-16',
'2024-03-20', 'TAKE BACK RETURN', 'MAIL', '订单1004第一行', '2024-03-18'),
+ (1004, 2, 508, 208, 15.00, 1500.00, 0.15, 0.22, 'N', 'O', '2024-03-16',
'2024-03-21', 'COLLECT COD', 'SHIP', '订单1004第二行', '2024-03-19'),
+ (1005, 1, 509, 209, 4.00, 400.00, 0.04, 0.08, 'N', 'O', '2024-04-02',
'2024-04-06', 'DELIVER IN PERSON', 'TRUCK', '订单1005第一行', '2024-04-04'),
+ (1005, 2, 510, 210, 5.50, 550.00, 0.05, 0.11, 'N', 'O', '2024-04-02',
'2024-04-07', 'NONE', 'AIR', '订单1005第二行', '2024-04-05');
+
+ INSERT INTO partsupp_1 (
+ ps_partkey, ps_suppkey, ps_availqty, ps_supplycost, ps_comment
+ ) VALUES
+ (501, 201, 100, 50.00, '零件501供应商201'),
+ (502, 202, 200, 75.00, '零件502供应商202'),
+ (503, 203, 150, 60.00, '零件503供应商203'),
+ (504, 204, 180, 65.00, '零件504供应商204'),
+ (505, 205, 120, 55.00, '零件505供应商205'),
+ (506, 206, 90, 70.00, '零件506供应商206'),
+ (507, 207, 160, 80.00, '零件507供应商207'),
+ (508, 208, 140, 75.00, '零件508供应商208'),
+ (509, 209, 110, 85.00, '零件509供应商209'),
+ (510, 210, 130, 90.00, '零件510供应商210');
+ """
+
+ sql """analyze table lineitem_1 with sync;"""
+ sql """analyze table orders_1 with sync;"""
+ sql """analyze table partsupp_1 with sync;"""
+ sql """alter table lineitem_1 modify column l_comment set stats
('row_count'='10');"""
+ sql """alter table orders_1 modify column o_comment set stats
('row_count'='8');"""
+ sql """alter table partsupp_1 modify column ps_comment set stats
('row_count'='10');"""
+
+
+ def compare_res = { def stmt, int orderByColumns = 1 ->
+ sql "SET enable_materialized_view_rewrite=false"
+ def orderStmt = " order by " + (1..orderByColumns).join(", ")
+ def origin_res = sql stmt + orderStmt
+ logger.info("origin_res: " + origin_res)
+ sql "SET enable_materialized_view_rewrite=true"
+ def mv_origin_res = sql stmt + orderStmt
+ logger.info("mv_origin_res: " + mv_origin_res)
+ assertTrue((mv_origin_res == [] && origin_res == []) ||
(mv_origin_res.size() == origin_res.size()))
+ for (int row = 0; row < mv_origin_res.size(); row++) {
+ assertTrue(mv_origin_res[row].size() == origin_res[row].size())
+ for (int col = 0; col < mv_origin_res[row].size(); col++) {
+ assertTrue(mv_origin_res[row][col] == origin_res[row][col])
+ }
+ }
+ }
+
+ def dropAllConstraints = { def tableName ->
+ def getConstraintsQuery = "SHOW CONSTRAINTS FROM ${tableName}"
+ def constraints = sql getConstraintsQuery
+ logger.info("needed deleted constraints : ${constraints}")
+ constraints.each { constraint ->
+ def constraintName = constraint[0]
+ def dropConstraintSQL = "ALTER TABLE ${tableName} DROP CONSTRAINT
${constraintName}"
+ sql dropConstraintSQL
+ logger.info("delete ${tableName} constraits : ${constraintName}")
+ }
+ }
+
+
+ // base mtmv + inner/left join without unique
+ def mv_stmt_1 = """
+ select l_shipdate, l_linenumber, l_partkey, l_orderkey, o_orderkey,
o_partkey, o_suppkey, o_custkey, ps_partkey, ps_suppkey, ps_availqty,
ps_supplycost from lineitem_1
+ inner join orders_1
+ on o_orderkey = l_orderkey
+ inner join partsupp_1
+ on ps_partkey = l_partkey and ps_suppkey = l_suppkey
+ """
+ def mv_stmt_2 = """
+ select l_shipdate, l_linenumber, l_partkey, l_orderkey, o_orderkey,
o_partkey, o_suppkey, o_custkey, ps_partkey, ps_suppkey, ps_availqty,
ps_supplycost from lineitem_1
+ left join orders_1
+ on o_orderkey = l_orderkey
+ left join partsupp_1
+ on ps_partkey = l_partkey and ps_suppkey = l_suppkey
+ """
+
+ // single table, use left
+ def query_1 = """select l_shipdate, l_linenumber, l_partkey, l_orderkey
from lineitem_1"""
+
+ // lineitem_1 join orders_1 use left
+ def query_2 = """select l_shipdate, l_linenumber, l_partkey, l_orderkey
+ from lineitem_1
+ inner join
+ orders_1
+ on o_orderkey = l_orderkey"""
+ def query_3 = """select l_shipdate, l_linenumber, l_partkey, l_orderkey
+ from lineitem_1
+ left join
+ orders_1
+ on o_orderkey = l_orderkey"""
+ // lineitem_1 join orders_1 use both
+ def query_4 = """select l_shipdate, l_linenumber, l_partkey, o_orderkey
+ from lineitem_1
+ inner join
+ orders_1
+ on o_orderkey = l_orderkey"""
+ def query_5 = """select l_shipdate, l_linenumber, l_partkey, o_orderkey
+ from lineitem_1
+ left join
+ orders_1
+ on o_orderkey = l_orderkey"""
+ // lineitem_1 join orders_1 use right
+ def query_6 = """select o_orderkey, o_partkey, o_suppkey, o_custkey
+ from lineitem_1
+ inner join
+ orders_1
+ on o_orderkey = l_orderkey"""
+ def query_7 = """select o_orderkey, o_partkey, o_suppkey, o_custkey
+ from lineitem_1
+ left join
+ orders_1
+ on o_orderkey = l_orderkey"""
+
+ // lineitem_1 join partsupp_1 use left
+ def query_8 = """select l_shipdate, l_linenumber, l_partkey, l_orderkey
+ from lineitem_1
+ inner join
+ partsupp_1
+ on ps_partkey = l_partkey and ps_suppkey =
l_suppkey"""
+ def query_9 = """select l_shipdate, l_linenumber, l_partkey, l_orderkey
+ from lineitem_1
+ left join partsupp_1
+ on ps_partkey = l_partkey and ps_suppkey =
l_suppkey"""
+ // lineitem_1 join partsupp_1 use both
+ def query_10 = """select l_shipdate, l_linenumber, l_partkey, ps_partkey
+ from lineitem_1
+ inner join
+ partsupp_1 on ps_partkey = l_partkey and
ps_suppkey = l_suppkey"""
+ def query_11 = """select l_shipdate, l_linenumber, l_partkey, ps_partkey
+ from lineitem_1
+ left join partsupp_1
+ on ps_partkey = l_partkey and ps_suppkey =
l_suppkey"""
+
+ // lineitem_1 join partsupp_1 use right
+ def query_12 = """select ps_partkey, ps_suppkey, ps_availqty, ps_supplycost
+ from lineitem_1
+ inner join
+ partsupp_1 on ps_partkey = l_partkey and
ps_suppkey = l_suppkey"""
+ def query_13 = """select ps_partkey, ps_suppkey, ps_availqty, ps_supplycost
+ from lineitem_1
+ left join partsupp_1
+ on ps_partkey = l_partkey and ps_suppkey =
l_suppkey"""
+
+
+ def query_list = [query_1, query_2, query_3, query_4, query_5, query_6,
query_7, query_8, query_9,
+ query_10, query_11, query_12, query_13]
+
+ dropAllConstraints("orders_1")
+ dropAllConstraints("lineitem_1")
+ dropAllConstraints("partsupp_1")
+
+ // lineitem -> orders (fk-pk)
+ // lineitem -> partsupp (fk-pk)
+ sql """alter table partsupp_1 add constraint pk primary key(ps_partkey,
ps_suppkey)"""
+ sql """alter table lineitem_1 add constraint fk1 foreign key (l_partkey,
l_suppkey) references partsupp_1(ps_partkey, ps_suppkey)"""
+ sql """alter table orders_1 add constraint pk primary key(o_orderkey)"""
+ sql """alter table lineitem_1 add constraint fk foreign key (l_orderkey)
references orders_1(o_orderkey)"""
+
+ def mv_name = "join_elim_star_pattern"
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ dropAllConstraints("orders_1")
+ dropAllConstraints("lineitem_1")
+ dropAllConstraints("partsupp_1")
+ // lineitem -> orders (fk-pk)
+ sql """alter table orders_1 add constraint pk primary key(o_orderkey)"""
+ sql """alter table lineitem_1 add constraint fk foreign key (l_orderkey)
references orders_1(o_orderkey)"""
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in [8, 10, 12]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [9, 11, 13]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders_1")
+ dropAllConstraints("lineitem_1")
+ dropAllConstraints("partsupp_1")
+ // lineitem -> partsupp (fk-pk)
+ sql """alter table partsupp_1 add constraint pk primary key(ps_partkey,
ps_suppkey)"""
+ sql """alter table lineitem_1 add constraint fk1 foreign key (l_partkey,
l_suppkey) references partsupp_1(ps_partkey, ps_suppkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in [2, 4, 6]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [3, 5, 7]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders_1")
+ dropAllConstraints("lineitem_1")
+ dropAllConstraints("partsupp_1")
+ // lineitem -> orders (u)
+ // lineitem -> partsupp (fk-pk)
+ sql """alter table partsupp_1 add constraint pk primary key (ps_partkey,
ps_suppkey)"""
+ sql """alter table lineitem_1 add constraint fk1 foreign key (l_partkey,
l_suppkey) references partsupp_1(ps_partkey, ps_suppkey)"""
+ sql """alter table orders_1 add constraint uk unique (o_orderkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in [2, 4, 6]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders_1")
+ dropAllConstraints("lineitem_1")
+ dropAllConstraints("partsupp_1")
+ // lineitem -> orders (fk-pk)
+ // lineitem -> partsupp (u)
+ sql """alter table partsupp_1 add constraint pk unique (ps_partkey,
ps_suppkey)"""
+ sql """alter table orders_1 add constraint pk primary key (o_orderkey)"""
+ sql """alter table lineitem_1 add constraint fk foreign key (l_orderkey)
references orders_1(o_orderkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in [8, 10, 12]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [1, 2, 3, 4, 5, 6, 7, 9, 11, 13]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders_1")
+ dropAllConstraints("lineitem_1")
+ dropAllConstraints("partsupp_1")
+ // lineitem -> orders (u)
+ // lineitem -> partsupp (u)
+ sql """alter table partsupp_1 add constraint pk unique (ps_partkey,
ps_suppkey)"""
+ sql """alter table orders_1 add constraint pk unique (o_orderkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [1, 2, 3, 4, 5, 6, 7, 9, 11, 13]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders_1")
+ dropAllConstraints("lineitem_1")
+ dropAllConstraints("partsupp_1")
+ // lineitem -> orders (u)
+ sql """alter table orders_1 add constraint pk unique (o_orderkey)"""
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [9, 11, 13]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders_1")
+ dropAllConstraints("lineitem_1")
+ dropAllConstraints("partsupp_1")
+ // lineitem -> partsupp (u)
+ sql """alter table partsupp_1 add constraint pk unique (ps_partkey,
ps_suppkey)"""
+
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in [3, 5, 7]) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders_1")
+ dropAllConstraints("lineitem_1")
+ dropAllConstraints("partsupp_1")
+ // without constraints
+ create_async_mv(db, mv_name, mv_stmt_1)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("inner mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+ create_async_mv(db, mv_name, mv_stmt_2)
+ for (int j = 1; j < query_list.size() + 1; j++) {
+ logger.info("left mv current query index: " + j)
+ if (j in []) {
+ mv_rewrite_success(query_list[j - 1], mv_name)
+ compare_res(query_list[j - 1], 4)
+ } else {
+ mv_rewrite_fail(query_list[j - 1], mv_name)
+ }
+ }
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name};"""
+
+
+ dropAllConstraints("orders_1")
+ dropAllConstraints("lineitem_1")
+ dropAllConstraints("partsupp_1")
+
+ // negative examplesorders_1, lineitem_1, partsupp_1
+ sql """alter table partsupp_1 add constraint pk unique (ps_partkey,
ps_suppkey)"""
+ async_mv_rewrite_fail(db,
+ """
+ select l_shipdate, l_linenumber, o_orderkey, ps_suppkey from
lineitem_1
+ inner join orders_1
+ on o_orderkey = l_orderkey
+ inner join partsupp_1
+ on ps_partkey = l_partkey or ps_suppkey = l_suppkey
+ """,
+ """
+ select l_shipdate, l_linenumber, o_orderkey
+ from lineitem_1
+ inner join orders_1 on o_orderkey = l_orderkey
+ """,
+ "${mv_name}_neg_2")
+ sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name}_neg_1;"""
+
+ sql """
+ drop table if exists orders_1;
+ drop table if exists lineitem_1;
+ drop table if exists partsupp_1;
+ """
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]