This is an automated email from the ASF dual-hosted git repository.

englefly pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new d0654cd2930 [Feat](Nereids) add use mv hint (#40167)
d0654cd2930 is described below

commit d0654cd2930859877fcbafce0b7894e2f02ce335
Author: LiBinfeng <46676950+libinfeng...@users.noreply.github.com>
AuthorDate: Tue Sep 3 21:54:26 2024 +0800

    [Feat](Nereids) add use mv hint (#40167)
    
    support hint like:
    /*+ no_use_mv(tablename mvname) / which forbid tablename(indexname) to
    be choose
    or /+ use_mv(tablename mvname) */ which choose specific materialize view
---
 .../java/org/apache/doris/catalog/OlapTable.java   |  95 ++++++++++++++
 .../org/apache/doris/nereids/hint/UseMvHint.java   | 144 +++++++++++++++++++++
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  35 ++++-
 .../processor/pre/PullUpSubqueryAliasToCTE.java    |   2 +-
 .../doris/nereids/properties/SelectHintUseMv.java  |  53 ++++++++
 .../rules/analysis/EliminateLogicalSelectHint.java |  44 +++++--
 .../mv/AbstractSelectMaterializedIndexRule.java    |   2 +-
 .../trees/plans/logical/LogicalSelectHint.java     |  30 +++--
 .../joinorder/joinhint/DistributeHintTest.java     |   9 +-
 .../suites/nereids_p0/hint/test_use_mv.groovy      | 111 ++++++++++++++++
 10 files changed, 490 insertions(+), 35 deletions(-)

diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
index 01f0bb900ea..533c24daa0e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
@@ -54,6 +54,8 @@ import org.apache.doris.mtmv.MTMVRefreshContext;
 import org.apache.doris.mtmv.MTMVRelatedTableIf;
 import org.apache.doris.mtmv.MTMVSnapshotIf;
 import org.apache.doris.mtmv.MTMVVersionSnapshot;
+import org.apache.doris.nereids.hint.Hint;
+import org.apache.doris.nereids.hint.UseMvHint;
 import org.apache.doris.persist.gson.GsonPostProcessable;
 import org.apache.doris.persist.gson.GsonUtils;
 import org.apache.doris.qe.ConnectContext;
@@ -565,6 +567,99 @@ public class OlapTable extends Table implements 
MTMVRelatedTableIf, GsonPostProc
         return visibleMVs;
     }
 
+    public Long getBestMvIdWithHint(List<Long> orderedMvs) {
+        Optional<UseMvHint> useMvHint = getUseMvHint("USE_MV");
+        Optional<UseMvHint> noUseMvHint = getUseMvHint("NO_USE_MV");
+        if (useMvHint.isPresent() && noUseMvHint.isPresent()) {
+            if 
(noUseMvHint.get().getNoUseMVName(this.name).contains(useMvHint.get().getUseMvName(this.name)))
 {
+                String errorMsg = "conflict mv exist in use_mv and no_use_mv 
in the same time"
+                        + useMvHint.get().getUseMvName(this.name);
+                useMvHint.get().setStatus(Hint.HintStatus.SYNTAX_ERROR);
+                useMvHint.get().setErrorMessage(errorMsg);
+                noUseMvHint.get().setStatus(Hint.HintStatus.SYNTAX_ERROR);
+                noUseMvHint.get().setErrorMessage(errorMsg);
+            }
+            return getMvIdWithUseMvHint(useMvHint.get(), orderedMvs);
+        } else if (useMvHint.isPresent()) {
+            return getMvIdWithUseMvHint(useMvHint.get(), orderedMvs);
+        } else if (noUseMvHint.isPresent()) {
+            return getMvIdWithNoUseMvHint(noUseMvHint.get(), orderedMvs);
+        }
+        return orderedMvs.get(0);
+    }
+
+    private Long getMvIdWithUseMvHint(UseMvHint useMvHint, List<Long> 
orderedMvs) {
+        if (useMvHint.isAllMv()) {
+            useMvHint.setStatus(Hint.HintStatus.SYNTAX_ERROR);
+            useMvHint.setErrorMessage("use_mv hint should only have one mv in 
one table: "
+                    + this.name);
+            return orderedMvs.get(0);
+        } else {
+            String mvName = useMvHint.getUseMvName(this.name);
+            if (mvName != null) {
+                if (mvName.equals("`*`")) {
+                    useMvHint.setStatus(Hint.HintStatus.SYNTAX_ERROR);
+                    useMvHint.setErrorMessage("use_mv hint should only have 
one mv in one table: "
+                            + this.name);
+                    return orderedMvs.get(0);
+                }
+                Long choosedIndexId = indexNameToId.get(mvName);
+                if (orderedMvs.contains(choosedIndexId)) {
+                    useMvHint.setStatus(Hint.HintStatus.SUCCESS);
+                    return choosedIndexId;
+                } else {
+                    useMvHint.setStatus(Hint.HintStatus.SYNTAX_ERROR);
+                    useMvHint.setErrorMessage("do not have mv: " + mvName + " 
in table: " + this.name);
+                }
+            }
+        }
+        return orderedMvs.get(0);
+    }
+
+    private Long getMvIdWithNoUseMvHint(UseMvHint noUseMvHint, List<Long> 
orderedMvs) {
+        if (noUseMvHint.isAllMv()) {
+            noUseMvHint.setStatus(Hint.HintStatus.SUCCESS);
+            return getBaseIndex().getId();
+        } else {
+            List<String> mvNames = noUseMvHint.getNoUseMVName(this.name);
+            Set<Long> forbiddenIndexIds = Sets.newHashSet();
+            for (int i = 0; i < mvNames.size(); i++) {
+                if (mvNames.get(i).equals("`*`")) {
+                    noUseMvHint.setStatus(Hint.HintStatus.SUCCESS);
+                    return getBaseIndex().getId();
+                }
+                if (hasMaterializedIndex(mvNames.get(i))) {
+                    Long forbiddenIndexId = indexNameToId.get(mvNames.get(i));
+                    forbiddenIndexIds.add(forbiddenIndexId);
+                } else {
+                    noUseMvHint.setStatus(Hint.HintStatus.SYNTAX_ERROR);
+                    noUseMvHint.setErrorMessage("do not have mv: " + 
mvNames.get(i) + " in table: " + this.name);
+                    break;
+                }
+            }
+            for (int i = 0; i < orderedMvs.size(); i++) {
+                if (forbiddenIndexIds.contains(orderedMvs.get(i))) {
+                    noUseMvHint.setStatus(Hint.HintStatus.SUCCESS);
+                } else {
+                    return orderedMvs.get(i);
+                }
+            }
+        }
+        return orderedMvs.get(0);
+    }
+
+    private Optional<UseMvHint> getUseMvHint(String useMvName) {
+        for (Hint hint : 
ConnectContext.get().getStatementContext().getHints()) {
+            if (hint.isSyntaxError()) {
+                continue;
+            }
+            if (hint.getHintName().equalsIgnoreCase(useMvName)) {
+                return Optional.of((UseMvHint) hint);
+            }
+        }
+        return Optional.empty();
+    }
+
     public List<MaterializedIndex> getVisibleIndex() {
         Optional<Partition> partition = 
idToPartition.values().stream().findFirst();
         if (!partition.isPresent()) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/hint/UseMvHint.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/hint/UseMvHint.java
new file mode 100644
index 00000000000..5e37bdc2760
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/hint/UseMvHint.java
@@ -0,0 +1,144 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.hint;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * rule hint.
+ */
+public class UseMvHint extends Hint {
+
+    private final boolean isUseMv;
+
+    private final boolean isAllMv;
+
+    private final List<String> parameters;
+
+    private final Map<String, String> useMvTableColumnMap;
+
+    private final Map<String, List<String>> noUseMvTableColumnMap;
+
+    /**
+     * constructor of use mv hint
+     * @param hintName use mv
+     * @param parameters original parameters
+     * @param isUseMv use_mv hint or no_use_mv hint
+     * @param isAllMv should all mv be controlled
+     */
+    public UseMvHint(String hintName, List<String> parameters, boolean 
isUseMv, boolean isAllMv) {
+        super(hintName);
+        this.isUseMv = isUseMv;
+        this.isAllMv = isAllMv;
+        this.parameters = parameters;
+        this.useMvTableColumnMap = initUseMvTableColumnMap(parameters);
+        this.noUseMvTableColumnMap = initNoUseMvTableColumnMap(parameters);
+    }
+
+    private Map<String, String> initUseMvTableColumnMap(List<String> 
parameters) {
+        Map<String, String> tempUseMvTableColumnMap = new HashMap<>();
+        if (!isUseMv) {
+            return tempUseMvTableColumnMap;
+        }
+        if (parameters.size() % 2 == 1) {
+            this.setStatus(HintStatus.SYNTAX_ERROR);
+            this.setErrorMessage("parameter of use_mv hint must be in pairs");
+            return tempUseMvTableColumnMap;
+        }
+        for (int i = 0; i < parameters.size(); i += 2) {
+            String tableName = parameters.get(i);
+            String columnName = parameters.get(i + 1);
+            if (tempUseMvTableColumnMap.containsKey(tableName)) {
+                this.setStatus(HintStatus.SYNTAX_ERROR);
+                this.setErrorMessage("use_mv hint should only have one mv in 
one table: "
+                        + tableName + "." + columnName);
+                break;
+            }
+            tempUseMvTableColumnMap.put(tableName, columnName);
+        }
+        return tempUseMvTableColumnMap;
+    }
+
+    private Map<String, List<String>> initNoUseMvTableColumnMap(List<String> 
parameters) {
+        Map<String, List<String>> tempNoUseMvTableColumnMap = new HashMap<>();
+        if (isUseMv) {
+            return tempNoUseMvTableColumnMap;
+        }
+        if (parameters.size() % 2 == 1) {
+            this.setStatus(HintStatus.SYNTAX_ERROR);
+            this.setErrorMessage("parameter of no_use_mv hint must be in 
pairs");
+            return tempNoUseMvTableColumnMap;
+        }
+        for (int i = 0; i < parameters.size(); i += 2) {
+            String tableName = parameters.get(i);
+            String columnName = parameters.get(i + 1);
+            if (tempNoUseMvTableColumnMap.containsKey(tableName)) {
+                tempNoUseMvTableColumnMap.get(tableName).add(columnName);
+            } else {
+                List<String> list = new ArrayList<>();
+                list.add(columnName);
+                tempNoUseMvTableColumnMap.put(tableName, list);
+            }
+        }
+        return tempNoUseMvTableColumnMap;
+    }
+
+    public boolean isUseMv() {
+        return isUseMv;
+    }
+
+    public boolean isAllMv() {
+        return isAllMv;
+    }
+
+    public String getUseMvName(String tableName) {
+        return useMvTableColumnMap.get(tableName);
+    }
+
+    public List<String> getNoUseMVName(String tableName) {
+        return noUseMvTableColumnMap.get(tableName);
+    }
+
+    @Override
+    public String getExplainString() {
+        StringBuilder out = new StringBuilder();
+        if (isUseMv) {
+            out.append("use_mv");
+        } else {
+            out.append("no_use_mv");
+        }
+        if (!parameters.isEmpty()) {
+            out.append("(");
+            for (int i = 0; i < parameters.size(); i++) {
+                if (i % 2 == 0) {
+                    out.append(parameters.get(i));
+                } else {
+                    out.append(".");
+                    out.append(parameters.get(i));
+                    out.append(" ");
+                }
+            }
+            out.append(")");
+        }
+
+        return out.toString();
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index 7369b714f74..324ab808226 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -232,6 +232,7 @@ import 
org.apache.doris.nereids.properties.SelectHintLeading;
 import org.apache.doris.nereids.properties.SelectHintOrdered;
 import org.apache.doris.nereids.properties.SelectHintSetVar;
 import org.apache.doris.nereids.properties.SelectHintUseCboRule;
+import org.apache.doris.nereids.properties.SelectHintUseMv;
 import org.apache.doris.nereids.trees.TableSample;
 import org.apache.doris.nereids.trees.expressions.Add;
 import org.apache.doris.nereids.trees.expressions.And;
@@ -3165,7 +3166,7 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         if (hintContexts.isEmpty()) {
             return logicalPlan;
         }
-        Map<String, SelectHint> hints = Maps.newLinkedHashMap();
+        ImmutableList.Builder<SelectHint> hints = ImmutableList.builder();
         for (ParserRuleContext hintContext : hintContexts) {
             SelectHintContext selectHintContext = (SelectHintContext) 
hintContext;
             for (HintStatementContext hintStatement : 
selectHintContext.hintStatements) {
@@ -3187,7 +3188,7 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
                                 parameters.put(parameterName, value);
                             }
                         }
-                        hints.put(hintName, new SelectHintSetVar(hintName, 
parameters));
+                        hints.add(new SelectHintSetVar(hintName, parameters));
                         break;
                     case "leading":
                         List<String> leadingParameters = new ArrayList<>();
@@ -3197,10 +3198,10 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
                                 leadingParameters.add(parameterName);
                             }
                         }
-                        hints.put(hintName, new SelectHintLeading(hintName, 
leadingParameters));
+                        hints.add(new SelectHintLeading(hintName, 
leadingParameters));
                         break;
                     case "ordered":
-                        hints.put(hintName, new SelectHintOrdered(hintName));
+                        hints.add(new SelectHintOrdered(hintName));
                         break;
                     case "use_cbo_rule":
                         List<String> useRuleParameters = new ArrayList<>();
@@ -3210,7 +3211,7 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
                                 useRuleParameters.add(parameterName);
                             }
                         }
-                        hints.put(hintName, new SelectHintUseCboRule(hintName, 
useRuleParameters, false));
+                        hints.add(new SelectHintUseCboRule(hintName, 
useRuleParameters, false));
                         break;
                     case "no_use_cbo_rule":
                         List<String> noUseRuleParameters = new ArrayList<>();
@@ -3220,14 +3221,34 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
                                 noUseRuleParameters.add(parameterName);
                             }
                         }
-                        hints.put(hintName, new SelectHintUseCboRule(hintName, 
noUseRuleParameters, true));
+                        hints.add(new SelectHintUseCboRule(hintName, 
noUseRuleParameters, true));
+                        break;
+                    case "use_mv":
+                        List<String> useIndexParameters = new 
ArrayList<String>();
+                        for (HintAssignmentContext kv : 
hintStatement.parameters) {
+                            String parameterName = 
visitIdentifierOrText(kv.key);
+                            if (kv.key != null) {
+                                useIndexParameters.add(parameterName);
+                            }
+                        }
+                        hints.add(new SelectHintUseMv(hintName, 
useIndexParameters, true));
+                        break;
+                    case "no_use_mv":
+                        List<String> noUseIndexParameters = new 
ArrayList<String>();
+                        for (HintAssignmentContext kv : 
hintStatement.parameters) {
+                            String parameterName = 
visitIdentifierOrText(kv.key);
+                            if (kv.key != null) {
+                                noUseIndexParameters.add(parameterName);
+                            }
+                        }
+                        hints.add(new SelectHintUseMv(hintName, 
noUseIndexParameters, false));
                         break;
                     default:
                         break;
                 }
             }
         }
-        return new LogicalSelectHint<>(hints, logicalPlan);
+        return new LogicalSelectHint<>(hints.build(), logicalPlan);
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/pre/PullUpSubqueryAliasToCTE.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/pre/PullUpSubqueryAliasToCTE.java
index 8e8889f5e62..31a205d5ed5 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/pre/PullUpSubqueryAliasToCTE.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/pre/PullUpSubqueryAliasToCTE.java
@@ -59,7 +59,7 @@ public class PullUpSubqueryAliasToCTE extends 
PlanPreprocessor {
     public Plan visitLogicalSubQueryAlias(LogicalSubQueryAlias<? extends Plan> 
alias,
                                           StatementContext context) {
         if (alias.child() instanceof LogicalSelectHint
-                && ((LogicalSelectHint) alias.child()).isIncludeLeading()) {
+                && ((LogicalSelectHint) 
alias.child()).isIncludeHint("Leading")) {
             aliasQueries.add((LogicalSubQueryAlias<Plan>) alias);
             List<String> tableName = new ArrayList<>();
             tableName.add(alias.getAlias());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/SelectHintUseMv.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/SelectHintUseMv.java
new file mode 100644
index 00000000000..35ce25fb4f4
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/SelectHintUseMv.java
@@ -0,0 +1,53 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.properties;
+
+import java.util.List;
+
+/**
+ * select hint UseMv.
+ */
+public class SelectHintUseMv extends SelectHint {
+    private final List<String> parameters;
+
+    private final boolean isUseMv;
+
+    public SelectHintUseMv(String hintName, List<String> parameters, boolean 
isUseMv) {
+        super(hintName);
+        this.parameters = parameters;
+        this.isUseMv = isUseMv;
+    }
+
+    public List<String> getParameters() {
+        return parameters;
+    }
+
+    public boolean isUseMv() {
+        return isUseMv;
+    }
+
+    @Override
+    public String getHintName() {
+        return super.getHintName();
+    }
+
+    @Override
+    public String toString() {
+        return super.getHintName();
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/EliminateLogicalSelectHint.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/EliminateLogicalSelectHint.java
index ea2c9994606..ebff9f838a4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/EliminateLogicalSelectHint.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/EliminateLogicalSelectHint.java
@@ -27,10 +27,12 @@ import org.apache.doris.nereids.hint.Hint;
 import org.apache.doris.nereids.hint.LeadingHint;
 import org.apache.doris.nereids.hint.OrderedHint;
 import org.apache.doris.nereids.hint.UseCboRuleHint;
+import org.apache.doris.nereids.hint.UseMvHint;
 import org.apache.doris.nereids.properties.SelectHint;
 import org.apache.doris.nereids.properties.SelectHintLeading;
 import org.apache.doris.nereids.properties.SelectHintSetVar;
 import org.apache.doris.nereids.properties.SelectHintUseCboRule;
+import org.apache.doris.nereids.properties.SelectHintUseMv;
 import org.apache.doris.nereids.rules.Rule;
 import org.apache.doris.nereids.rules.RuleType;
 import org.apache.doris.nereids.rules.rewrite.OneRewriteRuleFactory;
@@ -43,7 +45,6 @@ import org.apache.doris.qe.VariableMgr;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
 
@@ -57,10 +58,10 @@ public class EliminateLogicalSelectHint extends 
OneRewriteRuleFactory {
     public Rule build() {
         return logicalSelectHint().thenApply(ctx -> {
             LogicalSelectHint<Plan> selectHintPlan = ctx.root;
-            for (Entry<String, SelectHint> hint : 
selectHintPlan.getHints().entrySet()) {
-                String hintName = hint.getKey();
+            for (SelectHint hint : selectHintPlan.getHints()) {
+                String hintName = hint.getHintName();
                 if (hintName.equalsIgnoreCase("SET_VAR")) {
-                    setVar((SelectHintSetVar) hint.getValue(), 
ctx.statementContext);
+                    setVar((SelectHintSetVar) hint, ctx.statementContext);
                 } else if (hintName.equalsIgnoreCase("ORDERED")) {
                     try {
                         
ctx.cascadesContext.getConnectContext().getSessionVariable()
@@ -73,12 +74,16 @@ public class EliminateLogicalSelectHint extends 
OneRewriteRuleFactory {
                     ctx.cascadesContext.getHintMap().put("Ordered", ordered);
                     ctx.statementContext.addHint(ordered);
                 } else if (hintName.equalsIgnoreCase("LEADING")) {
-                    extractLeading((SelectHintLeading) hint.getValue(), 
ctx.cascadesContext,
-                            ctx.statementContext, selectHintPlan.getHints());
+                    extractLeading((SelectHintLeading) hint, 
ctx.cascadesContext,
+                            ctx.statementContext, selectHintPlan);
                 } else if (hintName.equalsIgnoreCase("USE_CBO_RULE")) {
-                    extractRule((SelectHintUseCboRule) hint.getValue(), 
ctx.statementContext);
+                    extractRule((SelectHintUseCboRule) hint, 
ctx.statementContext);
+                } else if (hintName.equalsIgnoreCase("USE_MV")) {
+                    extractMv((SelectHintUseMv) hint, 
ConnectContext.get().getStatementContext());
+                } else if (hintName.equalsIgnoreCase("NO_USE_MV")) {
+                    extractMv((SelectHintUseMv) hint, 
ConnectContext.get().getStatementContext());
                 } else {
-                    logger.warn("Can not process select hint '{}' and skip 
it", hint.getKey());
+                    logger.warn("Can not process select hint '{}' and skip 
it", hint.getHintName());
                 }
             }
             return selectHintPlan.child();
@@ -116,7 +121,7 @@ public class EliminateLogicalSelectHint extends 
OneRewriteRuleFactory {
     }
 
     private void extractLeading(SelectHintLeading selectHint, CascadesContext 
context,
-                                    StatementContext statementContext, 
Map<String, SelectHint> hints) {
+                                    StatementContext statementContext, 
LogicalSelectHint<Plan> selectHintPlan) {
         LeadingHint hint = new LeadingHint("Leading", 
selectHint.getParameters(), selectHint.toString());
         if (context.getHintMap().get("Leading") != null) {
             hint.setStatus(Hint.HintStatus.SYNTAX_ERROR);
@@ -139,7 +144,8 @@ public class EliminateLogicalSelectHint extends 
OneRewriteRuleFactory {
         if (!hint.isSyntaxError()) {
             hint.setStatus(Hint.HintStatus.SUCCESS);
         }
-        if (hints.get("ordered") != null || 
ConnectContext.get().getSessionVariable().isDisableJoinReorder()
+        if (selectHintPlan.isIncludeHint("Ordered")
+                || 
ConnectContext.get().getSessionVariable().isDisableJoinReorder()
                 || context.isLeadingDisableJoinReorder()) {
             context.setLeadingJoin(false);
             hint.setStatus(Hint.HintStatus.UNUSED);
@@ -158,4 +164,22 @@ public class EliminateLogicalSelectHint extends 
OneRewriteRuleFactory {
         }
     }
 
+    private void extractMv(SelectHintUseMv selectHint, StatementContext 
statementContext) {
+        boolean isAllMv = selectHint.getParameters().isEmpty();
+        UseMvHint useMvHint = new UseMvHint(selectHint.getHintName(), 
selectHint.getParameters(),
+                selectHint.isUseMv(), isAllMv);
+        for (Hint hint : statementContext.getHints()) {
+            if (hint.getHintName().equals(selectHint.getHintName())) {
+                hint.setStatus(Hint.HintStatus.SYNTAX_ERROR);
+                hint.setErrorMessage("only one " + selectHint.getHintName() + 
" hint is allowed");
+                useMvHint.setStatus(Hint.HintStatus.SYNTAX_ERROR);
+                useMvHint.setErrorMessage("only one " + 
selectHint.getHintName() + " hint is allowed");
+            }
+        }
+        if (!useMvHint.isSyntaxError()) {
+            
ConnectContext.get().getSessionVariable().setEnableSyncMvCostBasedRewrite(false);
+        }
+        statementContext.addHint(useMvHint);
+    }
+
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/mv/AbstractSelectMaterializedIndexRule.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/mv/AbstractSelectMaterializedIndexRule.java
index 1124c141416..f17ab1c96bd 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/mv/AbstractSelectMaterializedIndexRule.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/mv/AbstractSelectMaterializedIndexRule.java
@@ -276,7 +276,7 @@ public abstract class AbstractSelectMaterializedIndexRule {
                         .thenComparing(rid -> (Long) rid))
                 .collect(Collectors.toList());
 
-        return sortedIndexIds.get(0);
+        return table.getBestMvIdWithHint(sortedIndexIds);
     }
 
     protected static List<MaterializedIndex> matchPrefixMost(
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSelectHint.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSelectHint.java
index 127889ea7ed..a33e2194131 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSelectHint.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSelectHint.java
@@ -29,10 +29,8 @@ import 
org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -44,9 +42,9 @@ import java.util.stream.Collectors;
 public class LogicalSelectHint<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE>
         implements BlockFuncDepsPropagation {
 
-    private final Map<String, SelectHint> hints;
+    private final ImmutableList<SelectHint> hints;
 
-    public LogicalSelectHint(Map<String, SelectHint> hints, CHILD_TYPE child) {
+    public LogicalSelectHint(ImmutableList<SelectHint> hints, CHILD_TYPE 
child) {
         this(hints, Optional.empty(), Optional.empty(), child);
     }
 
@@ -57,19 +55,29 @@ public class LogicalSelectHint<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHI
      * @param logicalProperties logicalProperties is use for compute output
      * @param child child plan
      */
-    public LogicalSelectHint(Map<String, SelectHint> hints,
+    public LogicalSelectHint(ImmutableList<SelectHint> hints,
             Optional<GroupExpression> groupExpression,
             Optional<LogicalProperties> logicalProperties, CHILD_TYPE child) {
         super(PlanType.LOGICAL_SELECT_HINT, groupExpression, 
logicalProperties, child);
-        this.hints = ImmutableMap.copyOf(Objects.requireNonNull(hints, "hints 
can not be null"));
+        this.hints = ImmutableList.copyOf(Objects.requireNonNull(hints, "hints 
can not be null"));
     }
 
-    public Map<String, SelectHint> getHints() {
+    public List<SelectHint> getHints() {
         return hints;
     }
 
-    public boolean isIncludeLeading() {
-        return hints.containsKey("leading");
+    /**
+     * check if current select hint include some hint
+     * @param hintName hint name
+     * @return boolean which indicate have hint
+     */
+    public boolean isIncludeHint(String hintName) {
+        for (SelectHint hint : hints) {
+            if (hint.getHintName().equalsIgnoreCase(hintName)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     @Override
@@ -107,9 +115,9 @@ public class LogicalSelectHint<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHI
 
     @Override
     public String toString() {
-        String hintStr = this.hints.entrySet()
+        String hintStr = this.hints
                 .stream()
-                .map(entry -> entry.getValue().toString())
+                .map(hint -> hint.toString())
                 .collect(Collectors.joining(", "));
         return "LogicalSelectHint (" + hintStr + ")";
     }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/joinorder/joinhint/DistributeHintTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/joinorder/joinhint/DistributeHintTest.java
index 0b2ed8069ad..f96fe7e918c 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/joinorder/joinhint/DistributeHintTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/joinorder/joinhint/DistributeHintTest.java
@@ -28,13 +28,12 @@ import org.apache.doris.nereids.util.HyperGraphBuilder;
 import org.apache.doris.nereids.util.MemoTestUtils;
 import org.apache.doris.nereids.util.PlanChecker;
 
-import com.google.common.collect.Maps;
+import com.google.common.collect.ImmutableList;
 import org.junit.jupiter.api.Test;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 public class DistributeHintTest extends TPCHTestBase {
@@ -85,15 +84,15 @@ public class DistributeHintTest extends TPCHTestBase {
     }
 
     private Plan generateLeadingHintPlan(int tableNum, Plan childPlan) {
-        Map<String, SelectHint> hints = Maps.newLinkedHashMap();
+        ImmutableList.Builder<SelectHint> hints = ImmutableList.builder();
         List<String> leadingParameters = new ArrayList<String>();
         for (int i = 0; i < tableNum; i++) {
             leadingParameters.add(String.valueOf(i));
         }
         Collections.shuffle(leadingParameters);
         System.out.println("LeadingHint: " + leadingParameters.toString());
-        hints.put("leading", new SelectHintLeading("leading", 
leadingParameters));
-        return new LogicalSelectHint<>(hints, childPlan);
+        hints.add(new SelectHintLeading("Leading", leadingParameters));
+        return new LogicalSelectHint<>(hints.build(), childPlan);
     }
 
     private void randomTest(int tableNum, int edgeNum, boolean withJoinHint, 
boolean withLeading) {
diff --git a/regression-test/suites/nereids_p0/hint/test_use_mv.groovy 
b/regression-test/suites/nereids_p0/hint/test_use_mv.groovy
new file mode 100644
index 00000000000..e511ccc11ae
--- /dev/null
+++ b/regression-test/suites/nereids_p0/hint/test_use_mv.groovy
@@ -0,0 +1,111 @@
+/*
+ * 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("test_use_mv") {
+    // create database and tables
+    sql 'DROP DATABASE IF EXISTS test_use_mv'
+    sql 'CREATE DATABASE IF NOT EXISTS test_use_mv'
+    sql 'use test_use_mv'
+
+    // setting planner to nereids
+    sql 'set exec_mem_limit=21G'
+    sql 'set be_number_for_test=1'
+    sql 'set parallel_pipeline_task_num=1'
+    sql "set disable_nereids_rules=PRUNE_EMPTY_PARTITION"
+    sql 'set enable_nereids_planner=true'
+    sql 'set enable_nereids_distribute_planner=false'
+    sql "set ignore_shape_nodes='PhysicalProject'"
+    sql 'set enable_fallback_to_original_planner=false'
+    sql 'set runtime_filter_mode=OFF'
+
+    sql """drop table if exists t1;"""
+    // create tables
+    sql """
+        CREATE TABLE `t1` (
+                          `k1` int(11) NULL,
+                          `k2` int(11) NULL,
+                          `v1` int(11) SUM NULL
+                        ) ENGINE=OLAP
+                        AGGREGATE KEY(`k1`, `k2`)
+                        COMMENT 'OLAP'
+                        DISTRIBUTED BY HASH(`k1`) BUCKETS 3
+                        PROPERTIES (
+                            "replication_allocation" = "tag.location.default: 
1",
+                            "in_memory" = "false",
+                            "storage_format" = "V2",
+                            "disable_auto_compaction" = "false"
+                        );
+    """
+    sql """ alter table t1 add rollup r1(k2, k1); """
+    waitForRollUpJob("t1", 5000, 1)
+    sql """ alter table t1 add rollup r2(k2); """
+    waitForRollUpJob("t1", 5000, 1)
+    createMV("create materialized view k1_k2_sumk3 as select k1, k2, sum(v1) 
from t1 group by k1, k2;")
+    sql """set enable_sync_mv_cost_based_rewrite = false"""
+    explain {
+        sql """select k1 from t1;"""
+        contains("t1(r1)")
+    }
+    sql """set enable_sync_mv_cost_based_rewrite = true"""
+    explain {
+        sql """select /*+ no_use_mv */ k1 from t1;"""
+        notContains("t1(r1)")
+    }
+    explain {
+        sql """select /*+ no_use_mv(t1) */ k1 from t1;"""
+        contains("parameter of no_use_mv hint must be in pairs")
+    }
+    explain {
+        sql """select /*+ no_use_mv(t1.`*`) */ k1 from t1;"""
+        contains("t1(t1)")
+    }
+    explain {
+        sql """select /*+ use_mv(t1.`*`) */ k1 from t1;"""
+        contains("use_mv hint should only have one mv in one table")
+    }
+    explain {
+        sql """select /*+ use_mv(t1.r1,t1.r2) */ k1 from t1;"""
+        contains("use_mv hint should only have one mv in one table")
+    }
+    explain {
+        sql """select /*+ use_mv(t1.r1) use_mv(t1.r2) */ k1 from t1;"""
+        contains("one use_mv hint is allowed")
+    }
+    explain {
+        sql """select /*+ no_use_mv(t1.r1) no_use_mv(t1.r2) */ k1 from t1;"""
+        contains("only one no_use_mv hint is allowed")
+    }
+    explain {
+        sql """select /*+ no_use_mv(t1.r3) */ k1 from t1;"""
+        contains("do not have mv: r3 in table: t1")
+    }
+    explain {
+        sql """select /*+ use_mv(t1.r1) no_use_mv(t1.r1) */ k1 from t1;"""
+        contains("conflict mv exist in use_mv and no_use_mv in the same time")
+    }
+    explain {
+        sql """select /*+ use_mv(t1.k1_k2_sumk3) */ k1, k2, sum(v1) from t1 
group by k1, k2;"""
+        contains("t1(k1_k2_sumk3)")
+    }
+    explain {
+        sql """select /*+ use_mv(t1.k1_k2_sumk3) */ k1, k2, min(v1) from t1 
group by k1, k2;"""
+        notContains("t1(k1_k2_sumk3)")
+    }
+
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org


Reply via email to