github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/hint/hint_processor.go (about)

     1  // Copyright 2020 WHTCORPS INC, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hint
    15  
    16  import (
    17  	"fmt"
    18  	"strconv"
    19  	"strings"
    20  
    21  	"github.com/whtcorpsinc/errors"
    22  	"github.com/whtcorpsinc/BerolinaSQL"
    23  	"github.com/whtcorpsinc/BerolinaSQL/ast"
    24  	"github.com/whtcorpsinc/BerolinaSQL/format"
    25  	"github.com/whtcorpsinc/BerolinaSQL/perceptron"
    26  	"github.com/whtcorpsinc/BerolinaSQL/terror"
    27  	"github.com/whtcorpsinc/milevadb/errno"
    28  	"github.com/whtcorpsinc/milevadb/stochastikctx"
    29  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    30  	"go.uber.org/zap"
    31  )
    32  
    33  var supportedHintNameForInsertStmt = map[string]struct{}{}
    34  
    35  func init() {
    36  	supportedHintNameForInsertStmt["memory_quota"] = struct{}{}
    37  }
    38  
    39  // HintsSet contains all hints of a query.
    40  type HintsSet struct {
    41  	blockHints [][]*ast.BlockOptimizerHint // Slice offset is the traversal order of `SelectStmt` in the ast.
    42  	indexHints [][]*ast.IndexHint          // Slice offset is the traversal order of `BlockName` in the ast.
    43  }
    44  
    45  // GetFirstBlockHints gets the first causet hints.
    46  func (hs *HintsSet) GetFirstBlockHints() []*ast.BlockOptimizerHint {
    47  	if len(hs.blockHints) > 0 {
    48  		return hs.blockHints[0]
    49  	}
    50  	return nil
    51  }
    52  
    53  // ContainBlockHint checks whether the causet hint set contains a hint.
    54  func (hs *HintsSet) ContainBlockHint(hint string) bool {
    55  	for _, blockHintsForBlock := range hs.blockHints {
    56  		for _, blockHint := range blockHintsForBlock {
    57  			if blockHint.HintName.String() == hint {
    58  				return true
    59  			}
    60  		}
    61  	}
    62  	return false
    63  }
    64  
    65  // ExtractBlockHintsFromStmtNode extracts causet hints from this node.
    66  func ExtractBlockHintsFromStmtNode(node ast.Node, sctx stochastikctx.Context) []*ast.BlockOptimizerHint {
    67  	switch x := node.(type) {
    68  	case *ast.SelectStmt:
    69  		return x.BlockHints
    70  	case *ast.UFIDelateStmt:
    71  		return x.BlockHints
    72  	case *ast.DeleteStmt:
    73  		return x.BlockHints
    74  	case *ast.InsertStmt:
    75  		//check duplicated hints
    76  		checkInsertStmtHintDuplicated(node, sctx)
    77  		return x.BlockHints
    78  	case *ast.ExplainStmt:
    79  		return ExtractBlockHintsFromStmtNode(x.Stmt, sctx)
    80  	default:
    81  		return nil
    82  	}
    83  }
    84  
    85  // checkInsertStmtHintDuplicated check whether existed the duplicated hints in both insertStmt and its selectStmt.
    86  // If existed, it would send a warning message.
    87  func checkInsertStmtHintDuplicated(node ast.Node, sctx stochastikctx.Context) {
    88  	switch x := node.(type) {
    89  	case *ast.InsertStmt:
    90  		if len(x.BlockHints) > 0 {
    91  			var supportedHint *ast.BlockOptimizerHint
    92  			for _, hint := range x.BlockHints {
    93  				if _, ok := supportedHintNameForInsertStmt[hint.HintName.L]; ok {
    94  					supportedHint = hint
    95  					break
    96  				}
    97  			}
    98  			if supportedHint != nil {
    99  				var duplicatedHint *ast.BlockOptimizerHint
   100  				for _, hint := range ExtractBlockHintsFromStmtNode(x.Select, nil) {
   101  					if hint.HintName.L == supportedHint.HintName.L {
   102  						duplicatedHint = hint
   103  						break
   104  					}
   105  				}
   106  				if duplicatedHint != nil {
   107  					hint := fmt.Sprintf("%s(`%v`)", duplicatedHint.HintName.O, duplicatedHint.HintData)
   108  					err := terror.ClassUtil.New(errno.ErrWarnConflictingHint, fmt.Sprintf(errno.MyALLEGROSQLErrName[errno.ErrWarnConflictingHint], hint))
   109  					sctx.GetStochastikVars().StmtCtx.AppendWarning(err)
   110  				}
   111  			}
   112  		}
   113  	default:
   114  		return
   115  	}
   116  }
   117  
   118  // RestoreOptimizerHints restores these hints.
   119  func RestoreOptimizerHints(hints []*ast.BlockOptimizerHint) string {
   120  	hintsStr := make([]string, 0, len(hints))
   121  	hintsMap := make(map[string]struct{}, len(hints))
   122  	for _, hint := range hints {
   123  		hintStr := RestoreBlockOptimizerHint(hint)
   124  		if _, ok := hintsMap[hintStr]; ok {
   125  			continue
   126  		}
   127  		hintsMap[hintStr] = struct{}{}
   128  		hintsStr = append(hintsStr, hintStr)
   129  	}
   130  	return strings.Join(hintsStr, ", ")
   131  }
   132  
   133  // RestoreBlockOptimizerHint returns string format of BlockOptimizerHint.
   134  func RestoreBlockOptimizerHint(hint *ast.BlockOptimizerHint) string {
   135  	var sb strings.Builder
   136  	ctx := format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)
   137  	err := hint.Restore(ctx)
   138  	// There won't be any error for optimizer hint.
   139  	if err != nil {
   140  		logutil.BgLogger().Debug("restore BlockOptimizerHint failed", zap.Error(err))
   141  	}
   142  	return strings.ToLower(sb.String())
   143  }
   144  
   145  // RestoreIndexHint returns string format of IndexHint.
   146  func RestoreIndexHint(hint *ast.IndexHint) (string, error) {
   147  	var sb strings.Builder
   148  	ctx := format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)
   149  	err := hint.Restore(ctx)
   150  	if err != nil {
   151  		logutil.BgLogger().Debug("restore IndexHint failed", zap.Error(err))
   152  		return "", err
   153  	}
   154  	return strings.ToLower(sb.String()), nil
   155  }
   156  
   157  // Restore returns the string format of HintsSet.
   158  func (hs *HintsSet) Restore() (string, error) {
   159  	hintsStr := make([]string, 0, len(hs.blockHints)+len(hs.indexHints))
   160  	for _, tblHints := range hs.blockHints {
   161  		for _, tblHint := range tblHints {
   162  			hintsStr = append(hintsStr, RestoreBlockOptimizerHint(tblHint))
   163  		}
   164  	}
   165  	for _, idxHints := range hs.indexHints {
   166  		for _, idxHint := range idxHints {
   167  			str, err := RestoreIndexHint(idxHint)
   168  			if err != nil {
   169  				return "", err
   170  			}
   171  			hintsStr = append(hintsStr, str)
   172  		}
   173  	}
   174  	return strings.Join(hintsStr, ", "), nil
   175  }
   176  
   177  type hintProcessor struct {
   178  	*HintsSet
   179  	// bindHint2Ast indicates the behavior of the processor, `true` for bind hint to ast, `false` for extract hint from ast.
   180  	bindHint2Ast bool
   181  	blockCounter int
   182  	indexCounter int
   183  }
   184  
   185  func (hp *hintProcessor) Enter(in ast.Node) (ast.Node, bool) {
   186  	switch v := in.(type) {
   187  	case *ast.SelectStmt:
   188  		if hp.bindHint2Ast {
   189  			if hp.blockCounter < len(hp.blockHints) {
   190  				v.BlockHints = hp.blockHints[hp.blockCounter]
   191  			} else {
   192  				v.BlockHints = nil
   193  			}
   194  			hp.blockCounter++
   195  		} else {
   196  			hp.blockHints = append(hp.blockHints, v.BlockHints)
   197  		}
   198  	case *ast.BlockName:
   199  		if hp.bindHint2Ast {
   200  			if hp.indexCounter < len(hp.indexHints) {
   201  				v.IndexHints = hp.indexHints[hp.indexCounter]
   202  			} else {
   203  				v.IndexHints = nil
   204  			}
   205  			hp.indexCounter++
   206  		} else {
   207  			hp.indexHints = append(hp.indexHints, v.IndexHints)
   208  		}
   209  	}
   210  	return in, false
   211  }
   212  
   213  func (hp *hintProcessor) Leave(in ast.Node) (ast.Node, bool) {
   214  	return in, true
   215  }
   216  
   217  // DefCauslectHint defCauslects hints for a memex.
   218  func DefCauslectHint(in ast.StmtNode) *HintsSet {
   219  	hp := hintProcessor{HintsSet: &HintsSet{blockHints: make([][]*ast.BlockOptimizerHint, 0, 4), indexHints: make([][]*ast.IndexHint, 0, 4)}}
   220  	in.Accept(&hp)
   221  	return hp.HintsSet
   222  }
   223  
   224  // BindHint will add hints for stmt according to the hints in `hintsSet`.
   225  func BindHint(stmt ast.StmtNode, hintsSet *HintsSet) ast.StmtNode {
   226  	hp := hintProcessor{HintsSet: hintsSet, bindHint2Ast: true}
   227  	stmt.Accept(&hp)
   228  	return stmt
   229  }
   230  
   231  // ParseHintsSet parses a ALLEGROALLEGROSQL string, then defCauslects and normalizes the HintsSet.
   232  func ParseHintsSet(p *BerolinaSQL.BerolinaSQL, allegrosql, charset, defCauslation, EDB string) (*HintsSet, []error, error) {
   233  	stmtNodes, warns, err := p.Parse(allegrosql, charset, defCauslation)
   234  	if err != nil {
   235  		return nil, nil, err
   236  	}
   237  	if len(stmtNodes) != 1 {
   238  		return nil, nil, errors.New(fmt.Sprintf("bind_sql must be a single memex: %s", allegrosql))
   239  	}
   240  	hs := DefCauslectHint(stmtNodes[0])
   241  	processor := &BlockHintProcessor{}
   242  	stmtNodes[0].Accept(processor)
   243  	for i, tblHints := range hs.blockHints {
   244  		newHints := make([]*ast.BlockOptimizerHint, 0, len(tblHints))
   245  		for _, tblHint := range tblHints {
   246  			if tblHint.HintName.L == hintQBName {
   247  				continue
   248  			}
   249  			offset := processor.GetHintOffset(tblHint.QBName, TypeSelect, i+1)
   250  			if offset < 0 || !processor.checkBlockQBName(tblHint.Blocks, TypeSelect) {
   251  				hintStr := RestoreBlockOptimizerHint(tblHint)
   252  				return nil, nil, errors.New(fmt.Sprintf("Unknown query causet name in hint %s", hintStr))
   253  			}
   254  			tblHint.QBName = GenerateQBName(TypeSelect, offset)
   255  			for i, tbl := range tblHint.Blocks {
   256  				if tbl.DBName.String() == "" {
   257  					tblHint.Blocks[i].DBName = perceptron.NewCIStr(EDB)
   258  				}
   259  			}
   260  			newHints = append(newHints, tblHint)
   261  		}
   262  		hs.blockHints[i] = newHints
   263  	}
   264  	return hs, extractHintWarns(warns), nil
   265  }
   266  
   267  func extractHintWarns(warns []error) []error {
   268  	for _, w := range warns {
   269  		if BerolinaSQL.ErrWarnOptimizerHintUnsupportedHint.Equal(w) ||
   270  			BerolinaSQL.ErrWarnOptimizerHintInvalidToken.Equal(w) ||
   271  			BerolinaSQL.ErrWarnMemoryQuotaOverflow.Equal(w) ||
   272  			BerolinaSQL.ErrWarnOptimizerHintParseError.Equal(w) ||
   273  			BerolinaSQL.ErrWarnOptimizerHintInvalidInteger.Equal(w) {
   274  			// Just one warning is enough, however we use a slice here to stop golint complaining
   275  			// "error should be the last type when returning multiple items" for `ParseHintsSet`.
   276  			return []error{w}
   277  		}
   278  	}
   279  	return nil
   280  }
   281  
   282  // BlockHintProcessor processes hints at different level of allegrosql memex.
   283  type BlockHintProcessor struct {
   284  	QbNameMap        map[string]int                    // Map from query causet name to select stmt offset.
   285  	QbHints          map[int][]*ast.BlockOptimizerHint // Group all hints at same query causet.
   286  	Ctx              stochastikctx.Context
   287  	selectStmtOffset int
   288  }
   289  
   290  // MaxSelectStmtOffset returns the current stmt offset.
   291  func (p *BlockHintProcessor) MaxSelectStmtOffset() int {
   292  	return p.selectStmtOffset
   293  }
   294  
   295  // Enter implements Visitor interface.
   296  func (p *BlockHintProcessor) Enter(in ast.Node) (ast.Node, bool) {
   297  	switch node := in.(type) {
   298  	case *ast.UFIDelateStmt:
   299  		p.checkQueryBlockHints(node.BlockHints, 0)
   300  	case *ast.DeleteStmt:
   301  		p.checkQueryBlockHints(node.BlockHints, 0)
   302  	case *ast.SelectStmt:
   303  		p.selectStmtOffset++
   304  		node.QueryBlockOffset = p.selectStmtOffset
   305  		p.checkQueryBlockHints(node.BlockHints, node.QueryBlockOffset)
   306  	}
   307  	return in, false
   308  }
   309  
   310  // Leave implements Visitor interface.
   311  func (p *BlockHintProcessor) Leave(in ast.Node) (ast.Node, bool) {
   312  	return in, true
   313  }
   314  
   315  const hintQBName = "qb_name"
   316  
   317  // checkQueryBlockHints checks the validity of query blocks and records the map of query causet name to select offset.
   318  func (p *BlockHintProcessor) checkQueryBlockHints(hints []*ast.BlockOptimizerHint, offset int) {
   319  	var qbName string
   320  	for _, hint := range hints {
   321  		if hint.HintName.L != hintQBName {
   322  			continue
   323  		}
   324  		if qbName != "" {
   325  			if p.Ctx != nil {
   326  				p.Ctx.GetStochastikVars().StmtCtx.AppendWarning(errors.New(fmt.Sprintf("There are more than two query names in same query causet,, using the first one %s", qbName)))
   327  			}
   328  		} else {
   329  			qbName = hint.QBName.L
   330  		}
   331  	}
   332  	if qbName == "" {
   333  		return
   334  	}
   335  	if p.QbNameMap == nil {
   336  		p.QbNameMap = make(map[string]int)
   337  	}
   338  	if _, ok := p.QbNameMap[qbName]; ok {
   339  		if p.Ctx != nil {
   340  			p.Ctx.GetStochastikVars().StmtCtx.AppendWarning(errors.New(fmt.Sprintf("Duplicate query causet name %s, only the first one is effective", qbName)))
   341  		}
   342  	} else {
   343  		p.QbNameMap[qbName] = offset
   344  	}
   345  }
   346  
   347  const (
   348  	defaultUFIDelateBlockName   = "uFIDel_1"
   349  	defaultDeleteBlockName   = "del_1"
   350  	defaultSelectBlockPrefix = "sel_"
   351  )
   352  
   353  // NodeType indicates if the node is for SELECT / UFIDelATE / DELETE.
   354  type NodeType int
   355  
   356  const (
   357  	// TypeUFIDelate for UFIDelate.
   358  	TypeUFIDelate NodeType = iota
   359  	// TypeDelete for DELETE.
   360  	TypeDelete
   361  	// TypeSelect for SELECT.
   362  	TypeSelect
   363  )
   364  
   365  // getBlockName finds the offset of query causet name. It use 0 as offset for top level uFIDelate or delete,
   366  // -1 for invalid causet name.
   367  func (p *BlockHintProcessor) getBlockOffset(blockName perceptron.CIStr, nodeType NodeType) int {
   368  	if p.QbNameMap != nil {
   369  		level, ok := p.QbNameMap[blockName.L]
   370  		if ok {
   371  			return level
   372  		}
   373  	}
   374  	// Handle the default query causet name.
   375  	if nodeType == TypeUFIDelate && blockName.L == defaultUFIDelateBlockName {
   376  		return 0
   377  	}
   378  	if nodeType == TypeDelete && blockName.L == defaultDeleteBlockName {
   379  		return 0
   380  	}
   381  	if nodeType == TypeSelect && strings.HasPrefix(blockName.L, defaultSelectBlockPrefix) {
   382  		suffix := blockName.L[len(defaultSelectBlockPrefix):]
   383  		level, err := strconv.ParseInt(suffix, 10, 64)
   384  		if err != nil || level > int64(p.selectStmtOffset) {
   385  			return -1
   386  		}
   387  		return int(level)
   388  	}
   389  	return -1
   390  }
   391  
   392  // GetHintOffset gets the offset of stmt that the hints take effects.
   393  func (p *BlockHintProcessor) GetHintOffset(qbName perceptron.CIStr, nodeType NodeType, currentOffset int) int {
   394  	if qbName.L != "" {
   395  		return p.getBlockOffset(qbName, nodeType)
   396  	}
   397  	return currentOffset
   398  }
   399  
   400  func (p *BlockHintProcessor) checkBlockQBName(blocks []ast.HintBlock, nodeType NodeType) bool {
   401  	for _, causet := range blocks {
   402  		if causet.QBName.L != "" && p.getBlockOffset(causet.QBName, nodeType) < 0 {
   403  			return false
   404  		}
   405  	}
   406  	return true
   407  }
   408  
   409  // GetCurrentStmtHints extracts all hints that take effects at current stmt.
   410  func (p *BlockHintProcessor) GetCurrentStmtHints(hints []*ast.BlockOptimizerHint, nodeType NodeType, currentOffset int) []*ast.BlockOptimizerHint {
   411  	if p.QbHints == nil {
   412  		p.QbHints = make(map[int][]*ast.BlockOptimizerHint)
   413  	}
   414  	for _, hint := range hints {
   415  		if hint.HintName.L == hintQBName {
   416  			continue
   417  		}
   418  		offset := p.GetHintOffset(hint.QBName, nodeType, currentOffset)
   419  		if offset < 0 || !p.checkBlockQBName(hint.Blocks, nodeType) {
   420  			hintStr := RestoreBlockOptimizerHint(hint)
   421  			p.Ctx.GetStochastikVars().StmtCtx.AppendWarning(errors.New(fmt.Sprintf("Hint %s is ignored due to unknown query causet name", hintStr)))
   422  			continue
   423  		}
   424  		p.QbHints[offset] = append(p.QbHints[offset], hint)
   425  	}
   426  	return p.QbHints[currentOffset]
   427  }
   428  
   429  // GenerateQBName builds QBName from offset.
   430  func GenerateQBName(nodeType NodeType, blockOffset int) perceptron.CIStr {
   431  	if nodeType == TypeDelete && blockOffset == 0 {
   432  		return perceptron.NewCIStr(defaultDeleteBlockName)
   433  	} else if nodeType == TypeUFIDelate && blockOffset == 0 {
   434  		return perceptron.NewCIStr(defaultUFIDelateBlockName)
   435  	}
   436  	return perceptron.NewCIStr(fmt.Sprintf("%s%d", defaultSelectBlockPrefix, blockOffset))
   437  }