Skip to content

Commit 35f3775

Browse files
[BugFix] Fix view based mv rewrite bug (backport #62198) (#62230)
Signed-off-by: shuming.li <ming.moriarty@gmail.com> Co-authored-by: shuming.li <ming.moriarty@gmail.com>
1 parent f948c7f commit 35f3775

File tree

12 files changed

+710
-36
lines changed

12 files changed

+710
-36
lines changed

docs/en/sql-reference/System_variable.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,12 @@ Used for MySQL client compatibility. No practical usage.
299299
* **Default**: true
300300
* **Introduced in**: v2.5.13, v3.0.7, v3.1.4, v3.2.0, v3.3.0
301301

302+
### enable_cbo_based_mv_rewrite
303+
304+
* **Description**: Whether to enable materialized view rewrite in CBO phase which can maximize the likelihood of successful query rewriting (e.g., when the join order differs between materialized views and queries), but it will increase the execution time of the optimizer phase.
305+
* **Default**: true
306+
* **Introduced in**: v3.5.5, v4.0.1
307+
302308
### enable_plan_advisor
303309

304310
* **Description**: Whether to enable Query Feedback feature for slow queries and manually marked queries.

docs/ja/sql-reference/System_variable.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ MySQL クライアント互換性のために使用されます。実際の用
298298
* **デフォルト**: true
299299
* **導入バージョン**: v2.5.13, v3.0.7, v3.1.4, v3.2.0, v3.3.0
300300

301+
### enable_cbo_based_mv_rewrite
302+
* **説明**: CBO フェーズでマテリアライズドビューの書き換えを有効にするかどうか。これにより、クエリ書き換えの成功率を最大化できます(例:マテリアライズドビューとクエリの結合順序が異なる場合)が、オプティマイザフェーズの実行時間が増加します。
303+
* **デフォルト**: true
304+
* **導入バージョン**: v3.5.5, v4.0.1
305+
301306
### enable_plan_advisor
302307

303308
* **説明**: 遅いクエリや手動でマークされたクエリに対するクエリフィードバック機能を有効にするかどうか。

docs/zh/sql-reference/System_variable.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,12 @@ ALTER USER 'jack' SET PROPERTIES ('session.query_timeout' = '600');
303303
* 默认值:true
304304
* 引入版本:v2.5.13,v3.0.7,v3.1.4,v3.2.0,v3.3.0
305305

306+
### enable_cbo_based_mv_rewrite
307+
308+
* 描述:是否在 CBO 阶段启用物化视图改写,这可以最大化查询改写成功的可能性(例如,当物化视图和查询之间的连接顺序不同时),但这会增加优化器阶段的执行时间。
309+
* 默认值:true
310+
* 引入版本:v3.5.5,v4.0.1
311+
306312
### enable_plan_advisor
307313

308314
* 描述:是否为慢查询或手动标记查询开启 Query Feedback 功能。

fe/fe-core/src/main/java/com/starrocks/qe/SessionVariable.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ public static MaterializedViewRewriteMode parse(String str) {
655655
public static final String ENABLE_VIEW_BASED_MV_REWRITE = "enable_view_based_mv_rewrite";
656656

657657
public static final String ENABLE_CBO_VIEW_BASED_MV_REWRITE = "enable_cbo_view_based_mv_rewrite";
658+
public static final String ENABLE_CBO_BASED_MV_REWRITE = "enable_cbo_based_mv_rewrite";
658659

659660
public static final String ENABLE_BIG_QUERY_LOG = "enable_big_query_log";
660661
public static final String BIG_QUERY_LOG_CPU_SECOND_THRESHOLD = "big_query_log_cpu_second_threshold";
@@ -2083,6 +2084,11 @@ public long getConnectorSinkTargetMaxFileSize() {
20832084
@VarAttr(name = ENABLE_CBO_VIEW_BASED_MV_REWRITE)
20842085
private boolean enableCBOViewBasedMvRewrite = false;
20852086

2087+
// Whether enable mv rewrite in CBO phase, true by default which means will try best to use mv
2088+
// to rewrite in RBO and CBO phase.
2089+
@VarAttr(name = ENABLE_CBO_BASED_MV_REWRITE)
2090+
private boolean enableCBOBasedMVRewrite = true;
2091+
20862092
/**
20872093
* Materialized view rewrite rule output limit: how many MVs would be chosen in a Rule for an OptExpr ?
20882094
*/
@@ -4075,6 +4081,14 @@ public boolean isEnableCBOViewBasedMvRewrite() {
40754081
return this.enableCBOViewBasedMvRewrite;
40764082
}
40774083

4084+
public boolean isEnableCBOBasedMVRewrite() {
4085+
return enableCBOBasedMVRewrite;
4086+
}
4087+
4088+
public void setEnableCboBasedMvRewrite(boolean enableCBOBasedMVRewrite) {
4089+
this.enableCBOBasedMVRewrite = enableCBOBasedMVRewrite;
4090+
}
4091+
40784092
public int getCboMaterializedViewRewriteRuleOutputLimit() {
40794093
return cboMaterializedViewRewriteRuleOutputLimit;
40804094
}

fe/fe-core/src/main/java/com/starrocks/sql/optimizer/Optimizer.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,6 @@ private OptExpression optimizeByCost(ConnectContext connectContext,
271271
}
272272

273273
memo.init(logicOperatorTree);
274-
if (context.getQueryMaterializationContext() != null) {
275-
// LogicalTreeWithView is logically equivalent to logicOperatorTree
276-
addViewBasedPlanIntoMemo(context.getQueryMaterializationContext().getQueryOptPlanWithView());
277-
}
278274
OptimizerTraceUtil.log("after logical rewrite, root group:\n%s", memo.getRootGroup());
279275

280276
// Currently, we cache output columns in logic property.
@@ -336,14 +332,6 @@ private OptExpression optimizeByCost(ConnectContext connectContext,
336332
}
337333
}
338334

339-
private void addViewBasedPlanIntoMemo(OptExpression logicalTreeWithView) {
340-
if (logicalTreeWithView == null) {
341-
return;
342-
}
343-
Memo memo = context.getMemo();
344-
memo.copyIn(memo.getRootGroup(), logicalTreeWithView);
345-
}
346-
347335
private void prepare(ConnectContext connectContext,
348336
ColumnRefFactory columnRefFactory,
349337
OptExpression logicOperatorTree) {
@@ -481,7 +469,7 @@ private void doRuleBasedMaterializedViewRewrite(OptExpression tree,
481469
}
482470
if (mvRewriteStrategy.enableForceRBORewrite) {
483471
// use rule based mv rewrite strategy to do mv rewrite for multi tables query
484-
if (mvRewriteStrategy.enableMultiTableRewrite) {
472+
if (mvRewriteStrategy.enableCBOBasedMvRewrite && mvRewriteStrategy.enableMultiTableRewrite) {
485473
ruleRewriteIterative(tree, rootTaskContext, RuleSetType.MULTI_TABLE_MV_REWRITE);
486474
}
487475
if (mvRewriteStrategy.enableSingleTableRewrite) {
@@ -767,6 +755,7 @@ private void viewBasedMvRuleRewrite(OptExpression tree, TaskContext rootTaskCont
767755
try (Timer ignored = Tracers.watchScope("MVViewRewrite")) {
768756
OptimizerTraceUtil.logMVRewriteRule("VIEW_BASED_MV_REWRITE", "try VIEW_BASED_MV_REWRITE");
769757
OptExpression treeWithView = queryMaterializationContext.getQueryOptPlanWithView();
758+
OptimizerTraceUtil.logOptExpression("before ViewBasedMvRuleRewrite:\n%s", tree);
770759
// should add a LogicalTreeAnchorOperator for rewrite
771760
treeWithView = OptExpression.create(new LogicalTreeAnchorOperator(), treeWithView);
772761
if (mvRewriteStrategy.enableMultiTableRewrite) {
@@ -791,6 +780,7 @@ private void viewBasedMvRuleRewrite(OptExpression tree, TaskContext rootTaskCont
791780
}
792781
OptimizerTraceUtil.logMVRewriteRule("VIEW_BASED_MV_REWRITE", "original view scans size: {}, " +
793782
"left view scans size: {}", origQueryViewScanOperators.size(), leftViewScanOperators.size());
783+
OptimizerTraceUtil.logOptExpression("after ViewBasedMvRuleRewrite:\n%s", tree);
794784
} catch (Exception e) {
795785
OptimizerTraceUtil.logMVRewriteRule("VIEW_BASED_MV_REWRITE",
796786
"single table view based mv rule rewrite failed.", e);

fe/fe-core/src/main/java/com/starrocks/sql/optimizer/operator/logical/LogicalViewScanOperator.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.starrocks.sql.optimizer.operator.logical;
1616

1717
import com.google.common.base.Preconditions;
18+
import com.google.common.collect.Maps;
1819
import com.starrocks.analysis.Expr;
1920
import com.starrocks.catalog.Column;
2021
import com.starrocks.catalog.Table;
@@ -26,6 +27,7 @@
2627
import com.starrocks.sql.optimizer.operator.OperatorType;
2728
import com.starrocks.sql.optimizer.operator.OperatorVisitor;
2829
import com.starrocks.sql.optimizer.operator.scalar.ColumnRefOperator;
30+
import com.starrocks.sql.optimizer.operator.scalar.ScalarOperator;
2931
import com.starrocks.sql.optimizer.rule.transformation.materialization.MvUtils;
3032

3133
import java.util.Map;
@@ -42,19 +44,25 @@ public class LogicalViewScanOperator extends LogicalScanOperator {
4244
// Original plan evaluator(inlined view) for the input logical plan tree which has not done rule based rewrite yet,
4345
// this is only used when the view scan operator cannot be rewritten by view-based rewrite rules.
4446
private OptPlanEvaluator optPlanEvaluator;
47+
// this maps original inlined view's column ref to non-inlined column ref which can be used for
48+
// replacing from non-inlined column refs to inlined view's column ref.
49+
private Map<ColumnRefOperator, ScalarOperator> originalColumnRefToInlinedColumnRefMap;
4550

4651
public LogicalViewScanOperator(
4752
int relationId,
4853
Table table,
4954
Map<ColumnRefOperator, Column> colRefToColumnMetaMap,
5055
Map<Column, ColumnRefOperator> columnMetaToColRefMap,
5156
ColumnRefSet outputColumnSet,
52-
Map<Expr, ColumnRefOperator> expressionToColumns) {
57+
Map<Expr, ColumnRefOperator> expressionToColumns,
58+
Map<ColumnRefOperator, ScalarOperator> originalColumnRefToInlinedColumnRefMap) {
5359
super(OperatorType.LOGICAL_VIEW_SCAN, table, colRefToColumnMetaMap,
5460
columnMetaToColRefMap, Operator.DEFAULT_LIMIT, null, null);
5561
this.relationId = relationId;
5662
this.outputColumnSet = outputColumnSet;
5763
this.expressionToColumns = expressionToColumns;
64+
// copy the originalColumnRefToInlinedColumnRefMap to avoid modification by projection
65+
this.originalColumnRefToInlinedColumnRefMap = Maps.newHashMap(originalColumnRefToInlinedColumnRefMap);
5866
}
5967

6068
private LogicalViewScanOperator() {
@@ -84,6 +92,10 @@ public OptExpression getOriginalPlanEvaluator() {
8492
return this.optPlanEvaluator.evaluate();
8593
}
8694

95+
public Map<ColumnRefOperator, ScalarOperator> getOriginalColumnRefToInlinedColumnRefMap() {
96+
return originalColumnRefToInlinedColumnRefMap;
97+
}
98+
8799
/**
88100
* Evaluate the original plan and return the optimized plan for the input logical plan tree which is with inlined view and
89101
* has not done rule based rewrite yet.
@@ -157,6 +169,7 @@ public LogicalViewScanOperator.Builder withOperator(LogicalViewScanOperator scan
157169
builder.expressionToColumns = scanOperator.expressionToColumns;
158170
builder.outputColumnSet = scanOperator.outputColumnSet;
159171
builder.optPlanEvaluator = scanOperator.optPlanEvaluator;
172+
builder.originalColumnRefToInlinedColumnRefMap = scanOperator.originalColumnRefToInlinedColumnRefMap;
160173
return this;
161174
}
162175
}

fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rule/transformation/materialization/MvRewriteStrategy.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public int getOrdinal() {
7070
public boolean enableViewBasedRewrite = false;
7171
public boolean enableSingleTableRewrite = false;
7272
public boolean enableMultiTableRewrite = false;
73+
public boolean enableCBOBasedMvRewrite = false;
7374

7475
static class MvStrategyArbitrator {
7576
private final OptimizerConfig optimizerConfig;
@@ -130,7 +131,7 @@ private boolean isEnableRBOSingleTableRewrite(OptExpression queryPlan) {
130131
return true;
131132
}
132133

133-
private boolean isEnableCBOMultiTableRewrite(OptExpression queryPlan) {
134+
private boolean isEnableMultiTableRewrite(OptExpression queryPlan) {
134135
if (!sessionVariable.isEnableMaterializedViewSingleTableViewDeltaRewrite() &&
135136
MvUtils.getAllTables(queryPlan).size() <= 1) {
136137
return false;
@@ -170,7 +171,8 @@ public static MvRewriteStrategy prepareRewriteStrategy(OptimizerContext optimize
170171
strategy.enableSingleTableRewrite = arbitrator.isEnableRBOSingleTableRewrite(queryPlan);
171172

172173
// cbo strategies
173-
strategy.enableMultiTableRewrite = arbitrator.isEnableCBOMultiTableRewrite(queryPlan);
174+
strategy.enableCBOBasedMvRewrite = sessionVariable.isEnableCBOBasedMVRewrite();
175+
strategy.enableMultiTableRewrite = arbitrator.isEnableMultiTableRewrite(queryPlan);
174176
return strategy;
175177
}
176178

fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rule/transformation/materialization/MvUtils.java

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import com.starrocks.sql.optimizer.Optimizer;
7373
import com.starrocks.sql.optimizer.OptimizerConfig;
7474
import com.starrocks.sql.optimizer.OptimizerContext;
75+
import com.starrocks.sql.optimizer.OptimizerTraceUtil;
7576
import com.starrocks.sql.optimizer.QueryMaterializationContext;
7677
import com.starrocks.sql.optimizer.Utils;
7778
import com.starrocks.sql.optimizer.base.ColumnRefFactory;
@@ -1180,11 +1181,17 @@ public static OptExpression replaceLogicalViewScanOperator(OptExpression queryEx
11801181
// add a LogicalTreeAnchorOperator to replace the tree easier
11811182
OptExpression anchorExpr = OptExpression.create(new LogicalTreeAnchorOperator(), queryExpression);
11821183
doReplaceLogicalViewScanOperator(anchorExpr, 0, queryExpression);
1184+
1185+
// check if there is any view scan operator in the tree
11831186
List<Operator> viewScanOperators = Lists.newArrayList();
11841187
MvUtils.collectViewScanOperator(anchorExpr, viewScanOperators);
11851188
if (!viewScanOperators.isEmpty()) {
1189+
OptimizerTraceUtil.logMVRewriteRule("VIEW_BASED_MV_REWRITE",
1190+
"After replacing logical view scan operator but found view scan operator in the tree, "
1191+
+ "so cannot rewrite the query expression: " + queryExpression);
11861192
return null;
11871193
}
1194+
11881195
OptExpression newQuery = anchorExpr.inputAt(0);
11891196
deriveLogicalProperty(newQuery);
11901197
return newQuery;
@@ -1196,29 +1203,48 @@ private static void doReplaceLogicalViewScanOperator(OptExpression parent,
11961203
LogicalOperator op = queryExpression.getOp().cast();
11971204
if (op instanceof LogicalViewScanOperator) {
11981205
LogicalViewScanOperator viewScanOperator = op.cast();
1199-
OptExpression viewPlan = viewScanOperator.getOriginalPlanEvaluator();
1206+
OptExpression inlineViewPlan = viewScanOperator.getOriginalPlanEvaluator();
12001207
if (viewScanOperator.getPredicate() != null) {
1201-
// If viewScanOperator contains predicate, we need to rewrite them,
1202-
// otherwise predicate will be lost.
1203-
Map<ColumnRefOperator, ScalarOperator> reverseColumnRefMap =
1204-
viewScanOperator.getProjection().getColumnRefMap().entrySet().stream()
1205-
.collect(Collectors.toMap(e -> (ColumnRefOperator) e.getValue(), e -> e.getKey()));
1206-
ReplaceColumnRefRewriter rewriter = new ReplaceColumnRefRewriter(reverseColumnRefMap);
1207-
Operator.Builder builder = OperatorBuilderFactory.build(viewPlan.getOp());
1208-
builder.withOperator(viewPlan.getOp());
1209-
// rewrite predicate
1210-
builder.setPredicate(rewriter.rewrite(viewScanOperator.getPredicate()));
1211-
// rewrite projection
1212-
Map<ColumnRefOperator, ScalarOperator> newColumnRefMap = viewScanOperator.getProjection().getColumnRefMap()
1208+
// original map records inlined view's column ref to non-inlined view's column ref mapping,
1209+
// now we need to rewrite non-inlined view's column ref to inlined view's column ref,
1210+
// so we need to reverse the mapping.
1211+
Map<ColumnRefOperator, ScalarOperator> originalColumnRefToInlinedColumnRefMap =
1212+
Maps.newHashMap(viewScanOperator.getOriginalColumnRefToInlinedColumnRefMap());
1213+
// add new added column ref mapping
1214+
if (viewScanOperator.getProjection() != null) {
1215+
viewScanOperator.getProjection().getColumnRefMap()
1216+
.entrySet()
1217+
.stream()
1218+
.forEach(e -> originalColumnRefToInlinedColumnRefMap.put(e.getKey(), e.getValue()));
1219+
}
1220+
Map<ColumnRefOperator, ScalarOperator> reverseColumnRefMap = originalColumnRefToInlinedColumnRefMap
12131221
.entrySet()
12141222
.stream()
1215-
.map(e -> Maps.immutableEntry(e.getKey(), rewriter.rewrite(e.getValue())))
1216-
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
1217-
builder.setProjection(new Projection(newColumnRefMap));
1218-
Operator newViewPlanOp = builder.build();
1219-
parent.setChild(index, OptExpression.create(newViewPlanOp, viewPlan.getInputs()));
1223+
.collect(Collectors.toMap(e -> (ColumnRefOperator) e.getValue(), e -> e.getKey()));
1224+
1225+
// If viewScanOperator contains predicate, we need to rewrite them, otherwise predicate will be lost.
1226+
ReplaceColumnRefRewriter rewriter = new ReplaceColumnRefRewriter(reverseColumnRefMap);
1227+
Operator.Builder builder = OperatorBuilderFactory.build(inlineViewPlan.getOp());
1228+
builder.withOperator(inlineViewPlan.getOp());
1229+
if (viewScanOperator.getPredicate() != null) {
1230+
// rewrite predicate
1231+
ScalarOperator rewrittenPredicate = rewriter.rewrite(viewScanOperator.getPredicate());
1232+
builder.setPredicate(rewrittenPredicate);
1233+
}
1234+
// rewrite projection
1235+
if (viewScanOperator.getPredicate() != null) {
1236+
Map<ColumnRefOperator, ScalarOperator> newColumnRefMap = viewScanOperator
1237+
.getProjection().getColumnRefMap()
1238+
.entrySet()
1239+
.stream()
1240+
.map(e -> Maps.immutableEntry(e.getKey(), rewriter.rewrite(e.getValue())))
1241+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
1242+
builder.setProjection(new Projection(newColumnRefMap));
1243+
}
1244+
Operator newInlineViewPlanOp = builder.build();
1245+
parent.setChild(index, OptExpression.create(newInlineViewPlanOp, inlineViewPlan.getInputs()));
12201246
} else {
1221-
parent.setChild(index, viewPlan);
1247+
parent.setChild(index, inlineViewPlan);
12221248
}
12231249
return;
12241250
}

fe/fe-core/src/main/java/com/starrocks/sql/optimizer/transformer/RelationTransformer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ private LogicalViewScanOperator buildViewScan(
907907

908908
LogicalViewScanOperator scanOperator = new LogicalViewScanOperator(relationId,
909909
node.getView(), columnRefOperatorToColumn, columnMetaToColRefMap,
910-
new ColumnRefSet(logicalPlan.getOutputColumn()), newExprMapping);
910+
new ColumnRefSet(logicalPlan.getOutputColumn()), newExprMapping, projectionMap);
911911
if (inlineView) {
912912
// add a projection to make sure output columns keep the same,
913913
// because LogicalViewScanOperator should be logically equivalent to logicalPlan

0 commit comments

Comments
 (0)