From d0654cd2930859877fcbafce0b7894e2f02ce335 Mon Sep 17 00:00:00 2001 From: LiBinfeng <46676950+LiBinfeng-01@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:54:26 +0800 Subject: [PATCH] [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 --- .../org/apache/doris/catalog/OlapTable.java | 95 ++++++++++++ .../apache/doris/nereids/hint/UseMvHint.java | 144 ++++++++++++++++++ .../nereids/parser/LogicalPlanBuilder.java | 35 ++++- .../pre/PullUpSubqueryAliasToCTE.java | 2 +- .../nereids/properties/SelectHintUseMv.java | 53 +++++++ .../analysis/EliminateLogicalSelectHint.java | 44 ++++-- .../AbstractSelectMaterializedIndexRule.java | 2 +- .../plans/logical/LogicalSelectHint.java | 30 ++-- .../joinhint/DistributeHintTest.java | 9 +- .../suites/nereids_p0/hint/test_use_mv.groovy | 111 ++++++++++++++ 10 files changed, 490 insertions(+), 35 deletions(-) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/hint/UseMvHint.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/properties/SelectHintUseMv.java create mode 100644 regression-test/suites/nereids_p0/hint/test_use_mv.groovy 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 01f0bb900eac24..533c24daa0e7bc 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.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 Map getVisibleIndexIdToMeta() { return visibleMVs; } + public Long getBestMvIdWithHint(List orderedMvs) { + Optional useMvHint = getUseMvHint("USE_MV"); + Optional 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 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 orderedMvs) { + if (noUseMvHint.isAllMv()) { + noUseMvHint.setStatus(Hint.HintStatus.SUCCESS); + return getBaseIndex().getId(); + } else { + List mvNames = noUseMvHint.getNoUseMVName(this.name); + Set 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 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 getVisibleIndex() { Optional 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 00000000000000..5e37bdc27603bc --- /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 parameters; + + private final Map useMvTableColumnMap; + + private final Map> 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 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 initUseMvTableColumnMap(List parameters) { + Map 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> initNoUseMvTableColumnMap(List parameters) { + Map> 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 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 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 7369b714f7439c..324ab808226930 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.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 @@ private LogicalPlan withSelectHint(LogicalPlan logicalPlan, List hints = Maps.newLinkedHashMap(); + ImmutableList.Builder hints = ImmutableList.builder(); for (ParserRuleContext hintContext : hintContexts) { SelectHintContext selectHintContext = (SelectHintContext) hintContext; for (HintStatementContext hintStatement : selectHintContext.hintStatements) { @@ -3187,7 +3188,7 @@ private LogicalPlan withSelectHint(LogicalPlan logicalPlan, List leadingParameters = new ArrayList<>(); @@ -3197,10 +3198,10 @@ private LogicalPlan withSelectHint(LogicalPlan logicalPlan, List useRuleParameters = new ArrayList<>(); @@ -3210,7 +3211,7 @@ private LogicalPlan withSelectHint(LogicalPlan logicalPlan, List noUseRuleParameters = new ArrayList<>(); @@ -3220,14 +3221,34 @@ private LogicalPlan withSelectHint(LogicalPlan logicalPlan, List useIndexParameters = new ArrayList(); + 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 noUseIndexParameters = new ArrayList(); + 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 8e8889f5e62df2..31a205d5ed5054 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 Plan visitUnboundResultSink(UnboundResultSink unboundResu public Plan visitLogicalSubQueryAlias(LogicalSubQueryAlias alias, StatementContext context) { if (alias.child() instanceof LogicalSelectHint - && ((LogicalSelectHint) alias.child()).isIncludeLeading()) { + && ((LogicalSelectHint) alias.child()).isIncludeHint("Leading")) { aliasQueries.add((LogicalSubQueryAlias) alias); List 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 00000000000000..35ce25fb4f47c6 --- /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 parameters; + + private final boolean isUseMv; + + public SelectHintUseMv(String hintName, List parameters, boolean isUseMv) { + super(hintName); + this.parameters = parameters; + this.isUseMv = isUseMv; + } + + public List 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 ea2c9994606502..ebff9f838a447e 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.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.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 selectHintPlan = ctx.root; - for (Entry 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 Rule build() { 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 @@ private void setVar(SelectHintSetVar selectHint, StatementContext context) { } private void extractLeading(SelectHintLeading selectHint, CascadesContext context, - StatementContext statementContext, Map hints) { + StatementContext statementContext, LogicalSelectHint 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 @@ private void extractLeading(SelectHintLeading selectHint, CascadesContext contex 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 @@ private void extractRule(SelectHintUseCboRule selectHint, StatementContext state } } + 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 1124c141416f3f..f17ab1c96bd9cf 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 @@ protected static long selectBestIndex( .thenComparing(rid -> (Long) rid)) .collect(Collectors.toList()); - return sortedIndexIds.get(0); + return table.getBestMvIdWithHint(sortedIndexIds); } protected static List 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 127889ea7ed471..a33e2194131c8d 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 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 @@ public class LogicalSelectHint extends LogicalUnary implements BlockFuncDepsPropagation { - private final Map hints; + private final ImmutableList hints; - public LogicalSelectHint(Map hints, CHILD_TYPE child) { + public LogicalSelectHint(ImmutableList hints, CHILD_TYPE child) { this(hints, Optional.empty(), Optional.empty(), child); } @@ -57,19 +55,29 @@ public LogicalSelectHint(Map hints, CHILD_TYPE child) { * @param logicalProperties logicalProperties is use for compute output * @param child child plan */ - public LogicalSelectHint(Map hints, + public LogicalSelectHint(ImmutableList hints, Optional groupExpression, Optional 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 getHints() { + public List 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 List computeOutput() { @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 0b2ed8069ade83..f96fe7e918c410 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.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 void testHintJoin() { } private Plan generateLeadingHintPlan(int tableNum, Plan childPlan) { - Map hints = Maps.newLinkedHashMap(); + ImmutableList.Builder hints = ImmutableList.builder(); List leadingParameters = new ArrayList(); 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 00000000000000..e511ccc11ae071 --- /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)") + } + +}