diff --git a/errno/errcode.go b/errno/errcode.go index 25daacbccd..29d79c3b8b 100644 --- a/errno/errcode.go +++ b/errno/errcode.go @@ -894,6 +894,7 @@ const ( ErrGeneratedColumnNonPrior = 3107 ErrDependentByGeneratedColumn = 3108 ErrGeneratedColumnRefAutoInc = 3109 + ErrWarnConflictingHint = 3126 ErrInvalidJSONText = 3140 ErrInvalidJSONPath = 3143 ErrInvalidTypeForJSON = 3146 diff --git a/errno/errname.go b/errno/errname.go index 6b50735cc3..ccd913165c 100644 --- a/errno/errname.go +++ b/errno/errname.go @@ -887,6 +887,7 @@ var MySQLErrName = map[uint16]string{ ErrGeneratedColumnNonPrior: "Generated column can refer only to generated columns defined prior to it.", ErrDependentByGeneratedColumn: "Column '%s' has a generated column dependency.", ErrGeneratedColumnRefAutoInc: "Generated column '%s' cannot refer to auto-increment column.", + ErrWarnConflictingHint: "Hint %s is ignored as conflicting/duplicated.", ErrInvalidFieldSize: "Invalid size for column '%s'.", ErrIncorrectType: "Incorrect type for argument %s in function %s.", ErrInvalidJSONData: "Invalid JSON data provided to function %s: %s", diff --git a/planner/core/common_plans.go b/planner/core/common_plans.go index 7045fffe03..b49ce48b10 100644 --- a/planner/core/common_plans.go +++ b/planner/core/common_plans.go @@ -833,7 +833,7 @@ func (e *Explain) RenderResult() error { } case ast.ExplainFormatHint: hints := GenHintsFromPhysicalPlan(e.TargetPlan) - hints = append(hints, hint.ExtractTableHintsFromStmtNode(e.ExecStmt)...) + hints = append(hints, hint.ExtractTableHintsFromStmtNode(e.ExecStmt, nil)...) e.Rows = append(e.Rows, []string{hint.RestoreOptimizerHints(hints)}) default: return errors.Errorf("explain format '%s' is not supported now", e.Format) diff --git a/planner/optimize.go b/planner/optimize.go index 8a0c1eb409..f2341661e2 100644 --- a/planner/optimize.go +++ b/planner/optimize.go @@ -52,7 +52,7 @@ func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in sctx.PrepareTSFuture(ctx) - tableHints := hint.ExtractTableHintsFromStmtNode(node) + tableHints := hint.ExtractTableHintsFromStmtNode(node, sctx) stmtHints, warns := handleStmtHints(tableHints) defer func() { sctx.GetSessionVars().StmtCtx.StmtHints = stmtHints diff --git a/session/session_test.go b/session/session_test.go index 808d54a244..d3a1dc8b87 100644 --- a/session/session_test.go +++ b/session/session_test.go @@ -3096,6 +3096,18 @@ func (s *testSessionSuite2) TestStmtHints(c *C) { c.Assert(tk.Se.GetSessionVars().StmtCtx.MemTracker.CheckBytesLimit(val), IsTrue) c.Assert(tk.Se.GetSessionVars().StmtCtx.GetWarnings()[0].Err.Error(), Equals, "Setting the MEMORY_QUOTA to 0 means no memory limit") + tk.MustExec("use test") + tk.MustExec("create table t1(a int);") + tk.MustExec("insert /*+ MEMORY_QUOTA(1 MB) */ into t1 (a) values (1);") + val = int64(1) * 1024 * 1024 + c.Assert(tk.Se.GetSessionVars().StmtCtx.MemTracker.CheckBytesLimit(val), IsTrue) + + tk.MustExec("insert /*+ MEMORY_QUOTA(1 MB) */ into t1 select /*+ MEMORY_QUOTA(3 MB) */ * from t1;") + val = int64(1) * 1024 * 1024 + c.Assert(tk.Se.GetSessionVars().StmtCtx.MemTracker.CheckBytesLimit(val), IsTrue) + c.Assert(tk.Se.GetSessionVars().StmtCtx.GetWarnings(), HasLen, 1) + c.Assert(tk.Se.GetSessionVars().StmtCtx.GetWarnings()[0].Err.Error(), Equals, "[util:3126]Hint MEMORY_QUOTA(`3145728`) is ignored as conflicting/duplicated.") + // Test NO_INDEX_MERGE hint tk.Se.GetSessionVars().SetEnableIndexMerge(true) tk.MustExec("select /*+ NO_INDEX_MERGE() */ 1;") diff --git a/util/hint/hint_processor.go b/util/hint/hint_processor.go index b93f655329..b1fd24ed20 100644 --- a/util/hint/hint_processor.go +++ b/util/hint/hint_processor.go @@ -23,11 +23,19 @@ import ( "github.com/pingcap/parser/ast" "github.com/pingcap/parser/format" "github.com/pingcap/parser/model" + "github.com/pingcap/parser/terror" + "github.com/pingcap/tidb/errno" "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/util/logutil" "go.uber.org/zap" ) +var supportedHintNameForInsertStmt = map[string]struct{}{} + +func init() { + supportedHintNameForInsertStmt["memory_quota"] = struct{}{} +} + // HintsSet contains all hints of a query. type HintsSet struct { tableHints [][]*ast.TableOptimizerHint // Slice offset is the traversal order of `SelectStmt` in the ast. @@ -55,7 +63,7 @@ func (hs *HintsSet) ContainTableHint(hint string) bool { } // ExtractTableHintsFromStmtNode extracts table hints from this node. -func ExtractTableHintsFromStmtNode(node ast.Node) []*ast.TableOptimizerHint { +func ExtractTableHintsFromStmtNode(node ast.Node, sctx sessionctx.Context) []*ast.TableOptimizerHint { switch x := node.(type) { case *ast.SelectStmt: return x.TableHints @@ -63,14 +71,50 @@ func ExtractTableHintsFromStmtNode(node ast.Node) []*ast.TableOptimizerHint { return x.TableHints case *ast.DeleteStmt: return x.TableHints - // TODO: support hint for InsertStmt + case *ast.InsertStmt: + //check duplicated hints + checkInsertStmtHintDuplicated(node, sctx) + return x.TableHints case *ast.ExplainStmt: - return ExtractTableHintsFromStmtNode(x.Stmt) + return ExtractTableHintsFromStmtNode(x.Stmt, sctx) default: return nil } } +// checkInsertStmtHintDuplicated check whether existed the duplicated hints in both insertStmt and its selectStmt. +// If existed, it would send a warning message. +func checkInsertStmtHintDuplicated(node ast.Node, sctx sessionctx.Context) { + switch x := node.(type) { + case *ast.InsertStmt: + if len(x.TableHints) > 0 { + var supportedHint *ast.TableOptimizerHint + for _, hint := range x.TableHints { + if _, ok := supportedHintNameForInsertStmt[hint.HintName.L]; ok { + supportedHint = hint + break + } + } + if supportedHint != nil { + var duplicatedHint *ast.TableOptimizerHint + for _, hint := range ExtractTableHintsFromStmtNode(x.Select, nil) { + if hint.HintName.L == supportedHint.HintName.L { + duplicatedHint = hint + break + } + } + if duplicatedHint != nil { + hint := fmt.Sprintf("%s(`%v`)", duplicatedHint.HintName.O, duplicatedHint.HintData) + err := terror.ClassUtil.New(errno.ErrWarnConflictingHint, fmt.Sprintf(errno.MySQLErrName[errno.ErrWarnConflictingHint], hint)) + sctx.GetSessionVars().StmtCtx.AppendWarning(err) + } + } + } + default: + return + } +} + // RestoreOptimizerHints restores these hints. func RestoreOptimizerHints(hints []*ast.TableOptimizerHint) string { hintsStr := make([]string, 0, len(hints))