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 }