github.com/XiaoMi/Gaea@v1.2.5/proxy/server/executor.go (about) 1 // Copyright 2019 The Gaea Authors. All Rights Reserved. 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package server 16 17 import ( 18 "context" 19 "fmt" 20 "strconv" 21 "strings" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "github.com/XiaoMi/Gaea/backend" 27 "github.com/XiaoMi/Gaea/core/errors" 28 "github.com/XiaoMi/Gaea/log" 29 "github.com/XiaoMi/Gaea/mysql" 30 "github.com/XiaoMi/Gaea/parser" 31 "github.com/XiaoMi/Gaea/parser/ast" 32 "github.com/XiaoMi/Gaea/parser/format" 33 "github.com/XiaoMi/Gaea/proxy/plan" 34 "github.com/XiaoMi/Gaea/util" 35 "github.com/XiaoMi/Gaea/util/hack" 36 ) 37 38 const ( 39 // master comments 40 masterComment = "/*master*/" 41 // general query log variable 42 gaeaGeneralLogVariable = "gaea_general_log" 43 ) 44 45 // SessionExecutor is bound to a session, so requests are serializable 46 type SessionExecutor struct { 47 manager *Manager 48 49 namespace string 50 user string 51 db string 52 clientAddr string 53 54 status uint16 55 lastInsertID uint64 56 57 collation mysql.CollationID 58 charset string 59 sessionVariables *mysql.SessionVariables 60 61 txConns map[string]backend.PooledConnect 62 savepoints []string 63 txLock sync.Mutex 64 65 stmtID uint32 66 stmts map[uint32]*Stmt //prepare相关,client端到proxy的stmt 67 68 parser *parser.Parser 69 } 70 71 // Response response info 72 type Response struct { 73 RespType int 74 Status uint16 75 Data interface{} 76 } 77 78 const ( 79 // RespOK means OK message 80 RespOK = iota 81 // RespResult means Result message 82 RespResult 83 // RespError means error message 84 RespError 85 // RespFieldList means field list message 86 RespFieldList 87 // RespPrepare prepare response message 88 RespPrepare 89 // RespEOF means EOF message 90 RespEOF 91 // RespNoop means empty message 92 RespNoop 93 ) 94 95 // CreateOKResponse create ok response 96 func CreateOKResponse(status uint16) Response { 97 return Response{ 98 RespType: RespOK, 99 Status: status, 100 } 101 } 102 103 // CreateResultResponse create result response 104 func CreateResultResponse(status uint16, result *mysql.Result) Response { 105 return Response{ 106 RespType: RespResult, 107 Status: status, 108 Data: result, 109 } 110 } 111 112 // CreateErrorResponse create error response 113 func CreateErrorResponse(status uint16, err error) Response { 114 return Response{ 115 RespType: RespError, 116 Status: status, 117 Data: err, 118 } 119 } 120 121 // CreateFieldListResponse create field list response 122 func CreateFieldListResponse(status uint16, fl []*mysql.Field) Response { 123 return Response{ 124 RespType: RespFieldList, 125 Status: status, 126 Data: fl, 127 } 128 } 129 130 // CreatePrepareResponse create prepare response 131 func CreatePrepareResponse(status uint16, stmt *Stmt) Response { 132 return Response{ 133 RespType: RespPrepare, 134 Status: status, 135 Data: stmt, 136 } 137 } 138 139 // CreateEOFResponse create eof response 140 func CreateEOFResponse(status uint16) Response { 141 return Response{ 142 RespType: RespEOF, 143 Status: status, 144 } 145 } 146 147 // CreateNoopResponse no op response, for ComStmtClose 148 func CreateNoopResponse() Response { 149 return Response{ 150 RespType: RespNoop, 151 } 152 } 153 154 func newSessionExecutor(manager *Manager) *SessionExecutor { 155 156 return &SessionExecutor{ 157 sessionVariables: mysql.NewSessionVariables(), 158 txConns: make(map[string]backend.PooledConnect), 159 stmts: make(map[uint32]*Stmt), 160 parser: parser.New(), 161 status: initClientConnStatus, 162 manager: manager, 163 } 164 } 165 166 // GetNamespace return namespace in session 167 func (se *SessionExecutor) GetNamespace() *Namespace { 168 return se.manager.GetNamespace(se.namespace) 169 } 170 171 // GetVariables return variables in session 172 func (se *SessionExecutor) GetVariables() *mysql.SessionVariables { 173 return se.sessionVariables 174 } 175 176 func (se *SessionExecutor) setIntSessionVariable(name string, valueStr string) error { 177 if strings.ToLower(valueStr) == mysql.KeywordDefault { 178 se.sessionVariables.Delete(name) 179 return nil 180 } 181 182 value, err := strconv.ParseInt(valueStr, 10, 64) 183 if err != nil { 184 return err 185 } 186 if err = se.sessionVariables.Set(name, value); err != nil { 187 return err 188 } 189 return nil 190 } 191 192 func (se *SessionExecutor) setStringSessionVariable(name string, valueStr string) error { 193 if strings.ToLower(valueStr) == mysql.KeywordDefault { 194 se.sessionVariables.Delete(name) 195 return nil 196 } 197 198 return se.sessionVariables.Set(name, valueStr) 199 } 200 201 func (se *SessionExecutor) setGeneralLogVariable(valueStr string) error { 202 v, err := strconv.Atoi(valueStr) 203 if err != nil { 204 return errors.ErrInvalidArgument 205 } 206 atomic.StoreUint32(&ProcessGeneralLog, uint32(v)) 207 return nil 208 } 209 210 // GetLastInsertID return last_inert_id 211 func (se *SessionExecutor) GetLastInsertID() uint64 { 212 return se.lastInsertID 213 } 214 215 // SetLastInsertID store last_insert_id 216 func (se *SessionExecutor) SetLastInsertID(id uint64) { 217 se.lastInsertID = id 218 } 219 220 // GetStatus return session status 221 func (se *SessionExecutor) GetStatus() uint16 { 222 return se.status 223 } 224 225 // SetStatus store status 226 func (se *SessionExecutor) SetStatus(status uint16) { 227 se.status = status 228 } 229 230 // SetCollationID store collation id 231 func (se *SessionExecutor) SetCollationID(id mysql.CollationID) { 232 se.collation = id 233 } 234 235 // SetNamespaceDefaultCollationID store default collation id 236 func (se *SessionExecutor) SetNamespaceDefaultCollationID() { 237 se.collation = se.manager.GetNamespace(se.namespace).GetDefaultCollationID() 238 } 239 240 // GetCollationID return collation id 241 func (se *SessionExecutor) GetCollationID() mysql.CollationID { 242 return se.collation 243 } 244 245 // SetCharset set session charset 246 func (se *SessionExecutor) SetCharset(charset string) { 247 se.charset = charset 248 } 249 250 // SetNamespaceDefaultCharset set session default charset 251 func (se SessionExecutor) SetNamespaceDefaultCharset() { 252 se.charset = se.manager.GetNamespace(se.namespace).GetDefaultCharset() 253 } 254 255 // GetCharset return charset 256 func (se *SessionExecutor) GetCharset() string { 257 return se.charset 258 } 259 260 // SetDatabase set session database 261 func (se *SessionExecutor) SetDatabase(db string) { 262 se.db = db 263 } 264 265 // GetDatabase return database in session 266 func (se *SessionExecutor) GetDatabase() string { 267 return se.db 268 } 269 270 // ExecuteCommand execute command 271 func (se *SessionExecutor) ExecuteCommand(cmd byte, data []byte) Response { 272 switch cmd { 273 case mysql.ComQuit: 274 se.handleRollback(nil) 275 // https://dev.mysql.com/doc/internals/en/com-quit.html 276 // either a connection close or a OK_Packet, OK_Packet will cause client RST sometimes, but doesn't affect sql execute 277 return CreateNoopResponse() 278 case mysql.ComQuery: // data type: string[EOF] 279 sql := string(data) 280 // handle phase 281 r, err := se.handleQuery(sql) 282 if err != nil { 283 return CreateErrorResponse(se.status, err) 284 } 285 return CreateResultResponse(se.status, r) 286 case mysql.ComPing: 287 return CreateOKResponse(se.status) 288 case mysql.ComInitDB: 289 db := string(data) 290 // handle phase 291 err := se.handleUseDB(db) 292 if err != nil { 293 return CreateErrorResponse(se.status, err) 294 } 295 return CreateOKResponse(se.status) 296 case mysql.ComFieldList: 297 fs, err := se.handleFieldList(data) 298 if err != nil { 299 return CreateErrorResponse(se.status, err) 300 } 301 return CreateFieldListResponse(se.status, fs) 302 case mysql.ComStmtPrepare: 303 sql := string(data) 304 stmt, err := se.handleStmtPrepare(sql) 305 if err != nil { 306 return CreateErrorResponse(se.status, err) 307 } 308 return CreatePrepareResponse(se.status, stmt) 309 case mysql.ComStmtExecute: 310 values := make([]byte, len(data)) 311 copy(values, data) 312 r, err := se.handleStmtExecute(values) 313 if err != nil { 314 return CreateErrorResponse(se.status, err) 315 } 316 return CreateResultResponse(se.status, r) 317 case mysql.ComStmtClose: // no response 318 if err := se.handleStmtClose(data); err != nil { 319 return CreateErrorResponse(se.status, err) 320 } 321 return CreateNoopResponse() 322 case mysql.ComStmtSendLongData: // no response 323 values := make([]byte, len(data)) 324 copy(values, data) 325 if err := se.handleStmtSendLongData(values); err != nil { 326 return CreateErrorResponse(se.status, err) 327 } 328 return CreateNoopResponse() 329 case mysql.ComStmtReset: 330 if err := se.handleStmtReset(data); err != nil { 331 return CreateErrorResponse(se.status, err) 332 } 333 return CreateOKResponse(se.status) 334 case mysql.ComSetOption: 335 return CreateEOFResponse(se.status) 336 default: 337 msg := fmt.Sprintf("command %d not supported now", cmd) 338 log.Warn("dispatch command failed, error: %s", msg) 339 return CreateErrorResponse(se.status, mysql.NewError(mysql.ErrUnknown, msg)) 340 } 341 } 342 343 func (se *SessionExecutor) getBackendConns(sqls map[string]map[string][]string, fromSlave bool) (pcs map[string]backend.PooledConnect, err error) { 344 pcs = make(map[string]backend.PooledConnect) 345 for sliceName := range sqls { 346 var pc backend.PooledConnect 347 pc, err = se.getBackendConn(sliceName, fromSlave) 348 if err != nil { 349 return 350 } 351 pcs[sliceName] = pc 352 } 353 return 354 } 355 356 func (se *SessionExecutor) getBackendConn(sliceName string, fromSlave bool) (pc backend.PooledConnect, err error) { 357 if !se.isInTransaction() { 358 slice := se.GetNamespace().GetSlice(sliceName) 359 return slice.GetConn(fromSlave, se.GetNamespace().GetUserProperty(se.user)) 360 } 361 return se.getTransactionConn(sliceName) 362 } 363 364 func (se *SessionExecutor) getTransactionConn(sliceName string) (pc backend.PooledConnect, err error) { 365 se.txLock.Lock() 366 defer se.txLock.Unlock() 367 368 var ok bool 369 pc, ok = se.txConns[sliceName] 370 371 if !ok { 372 slice := se.GetNamespace().GetSlice(sliceName) // returns nil only when the conf is error (fatal) so panic is correct 373 if pc, err = slice.GetMasterConn(); err != nil { 374 return 375 } 376 377 if !se.isAutoCommit() { 378 if err = pc.SetAutoCommit(0); err != nil { 379 pc.Close() 380 pc.Recycle() 381 return 382 } 383 } else { 384 if err = pc.Begin(); err != nil { 385 pc.Close() 386 pc.Recycle() 387 return 388 } 389 } 390 for _, savepoint := range se.savepoints { 391 pc.Execute("savepoint "+savepoint, 0) 392 } 393 se.txConns[sliceName] = pc 394 } 395 396 return 397 } 398 399 func (se *SessionExecutor) recycleBackendConn(pc backend.PooledConnect, rollback bool) { 400 if pc == nil { 401 return 402 } 403 404 if se.isInTransaction() { 405 return 406 } 407 408 if rollback { 409 pc.Rollback() 410 } 411 412 pc.Recycle() 413 } 414 415 func (se *SessionExecutor) recycleBackendConns(pcs map[string]backend.PooledConnect, rollback bool) { 416 if se.isInTransaction() { 417 return 418 } 419 420 for _, pc := range pcs { 421 if pc == nil { 422 continue 423 } 424 if rollback { 425 pc.Rollback() 426 } 427 pc.Recycle() 428 } 429 } 430 431 func initBackendConn(pc backend.PooledConnect, phyDB string, charset string, collation mysql.CollationID, sessionVariables *mysql.SessionVariables) error { 432 if err := pc.UseDB(phyDB); err != nil { 433 return err 434 } 435 436 charsetChanged, err := pc.SetCharset(charset, collation) 437 if err != nil { 438 return err 439 } 440 441 variablesChanged, err := pc.SetSessionVariables(sessionVariables) 442 if err != nil { 443 return err 444 } 445 446 if charsetChanged || variablesChanged { 447 if err = pc.WriteSetStatement(); err != nil { 448 return err 449 } 450 } 451 452 return nil 453 } 454 455 func (se *SessionExecutor) executeInMultiSlices(reqCtx *util.RequestContext, pcs map[string]backend.PooledConnect, 456 sqls map[string]map[string][]string) ([]*mysql.Result, error) { 457 458 parallel := len(pcs) 459 if parallel != len(sqls) { 460 log.Warn("Session executeInMultiSlices error, conns: %v, sqls: %v, error: %s", pcs, sqls, errors.ErrConnNotEqual.Error()) 461 return nil, errors.ErrConnNotEqual 462 } else if parallel == 0 { 463 return nil, errors.ErrNoPlan 464 } 465 466 var ctx = context.Background() 467 var cancel context.CancelFunc 468 maxExecuteTime := se.manager.GetNamespace(se.namespace).GetMaxExecuteTime() 469 if maxExecuteTime > 0 { 470 ctx, cancel = context.WithTimeout(context.Background(), time.Duration(maxExecuteTime)*time.Millisecond) 471 defer cancel() 472 } 473 474 // Control go routine execution 475 done := make(chan string, parallel) 476 defer close(done) 477 478 // This map is not thread safe. 479 pcsUnCompleted := make(map[string]backend.PooledConnect, parallel) 480 for sliceName, pc := range pcs { 481 pcsUnCompleted[sliceName] = pc 482 } 483 484 resultCount := 0 485 for _, sqlSlice := range sqls { 486 for _, sqlDB := range sqlSlice { 487 resultCount += len(sqlDB) 488 } 489 } 490 rs := make([]interface{}, resultCount) 491 f := func(reqCtx *util.RequestContext, rs []interface{}, i int, sliceName string, execSqls map[string][]string, pc backend.PooledConnect) { 492 for db, sqls := range execSqls { 493 err := initBackendConn(pc, db, se.GetCharset(), se.GetCollationID(), se.GetVariables()) 494 if err != nil { 495 rs[i] = err 496 break 497 } 498 for _, v := range sqls { 499 startTime := time.Now() 500 r, err := pc.Execute(v, se.manager.GetNamespace(se.namespace).GetMaxResultSize()) 501 se.manager.RecordBackendSQLMetrics(reqCtx, se.namespace, v, pc.GetAddr(), startTime, err) 502 if err != nil { 503 rs[i] = err 504 } else { 505 rs[i] = r 506 } 507 i++ 508 } 509 } 510 done <- sliceName 511 } 512 513 offset := 0 514 for sliceName, pc := range pcs { 515 s := sqls[sliceName] //map[string][]string 516 go f(reqCtx, rs, offset, sliceName, s, pc) 517 for _, sqlDB := range sqls[sliceName] { 518 offset += len(sqlDB) 519 } 520 } 521 522 for i := 0; i < parallel; i++ { 523 select { 524 case sliceName := <-done: 525 delete(pcsUnCompleted, sliceName) 526 case <-ctx.Done(): 527 for sliceName, pc := range pcsUnCompleted { 528 connID := pc.GetConnectionID() 529 dc, err := se.manager.GetNamespace(se.namespace).GetSlice(sliceName).GetDirectConn(pc.GetAddr()) 530 if err != nil { 531 log.Warn("kill thread id: %d failed, get connection err: %v", connID, err.Error()) 532 continue 533 } 534 if _, err = dc.Execute(fmt.Sprintf("KILL QUERY %d", connID), 0); err != nil { 535 log.Warn("kill thread id: %d failed, err: %v", connID, err.Error()) 536 } 537 dc.Close() 538 } 539 for j := 0; j < len(pcsUnCompleted); j++ { 540 <-done 541 } 542 return nil, fmt.Errorf("%v %dms", errors.ErrTimeLimitExceeded, maxExecuteTime) 543 } 544 } 545 546 var err error 547 r := make([]*mysql.Result, resultCount) 548 for i, v := range rs { 549 if e, ok := v.(error); ok { 550 err = e 551 break 552 } 553 if rs[i] != nil { 554 r[i] = rs[i].(*mysql.Result) 555 } 556 } 557 return r, err 558 } 559 560 func canHandleWithoutPlan(stmtType int) bool { 561 return stmtType == parser.StmtShow || 562 stmtType == parser.StmtSet || 563 stmtType == parser.StmtBegin || 564 stmtType == parser.StmtCommit || 565 stmtType == parser.StmtRollback || 566 stmtType == parser.StmtSavepoint || 567 stmtType == parser.StmtUse 568 } 569 570 const variableRestoreFlag = format.RestoreKeyWordLowercase | format.RestoreNameLowercase 571 572 // 获取SET语句中变量的字符串值, 去掉各种引号并转换为小写 573 func getVariableExprResult(v ast.ExprNode) string { 574 s := &strings.Builder{} 575 ctx := format.NewRestoreCtx(variableRestoreFlag, s) 576 v.Restore(ctx) 577 return strings.ToLower(s.String()) 578 } 579 580 func getOnOffVariable(v string) (string, error) { 581 if v == "1" || v == "on" { 582 return "1", nil 583 } else if v == "0" || v == "off" { 584 return "0", nil 585 } else { 586 return "", fmt.Errorf("not an on off string") 587 } 588 } 589 590 // master-slave routing 591 func canExecuteFromSlave(c *SessionExecutor, sql string) bool { 592 if parser.Preview(sql) != parser.StmtSelect { 593 return false 594 } 595 596 _, comments := parser.SplitMarginComments(sql) 597 lcomment := strings.ToLower(strings.TrimSpace(comments.Leading)) 598 var fromSlave = c.GetNamespace().IsRWSplit(c.user) 599 if strings.ToLower(lcomment) == masterComment { 600 fromSlave = false 601 } 602 603 return fromSlave 604 } 605 606 // 如果是只读用户, 且SQL是INSERT, UPDATE, DELETE, 则拒绝执行, 返回true 607 func isSQLNotAllowedByUser(c *SessionExecutor, stmtType int) bool { 608 if c.GetNamespace().IsAllowWrite(c.user) { 609 return false 610 } 611 612 return stmtType == parser.StmtDelete || stmtType == parser.StmtInsert || stmtType == parser.StmtUpdate 613 } 614 615 func modifyResultStatus(r *mysql.Result, cc *SessionExecutor) { 616 r.Status = r.Status | cc.GetStatus() 617 } 618 619 func createShowDatabaseResult(dbs []string) *mysql.Result { 620 r := new(mysql.Resultset) 621 622 field := &mysql.Field{} 623 field.Name = hack.Slice("Database") 624 r.Fields = append(r.Fields, field) 625 626 for _, db := range dbs { 627 r.Values = append(r.Values, []interface{}{db}) 628 } 629 630 result := &mysql.Result{ 631 AffectedRows: uint64(len(dbs)), 632 Resultset: r, 633 } 634 635 plan.GenerateSelectResultRowData(result) 636 return result 637 } 638 639 func createShowGeneralLogResult() *mysql.Result { 640 r := new(mysql.Resultset) 641 642 field := &mysql.Field{} 643 field.Name = hack.Slice(gaeaGeneralLogVariable) 644 r.Fields = append(r.Fields, field) 645 646 var value string 647 if OpenProcessGeneralQueryLog() { 648 value = "ON" 649 } else { 650 value = "OFF" 651 } 652 r.Values = append(r.Values, []interface{}{value}) 653 result := &mysql.Result{ 654 AffectedRows: 1, 655 Resultset: r, 656 } 657 658 plan.GenerateSelectResultRowData(result) 659 return result 660 } 661 662 func getFromSlave(reqCtx *util.RequestContext) bool { 663 slaveFlag := reqCtx.Get(util.FromSlave) 664 if slaveFlag != nil && slaveFlag.(int) == 1 { 665 return true 666 } 667 668 return false 669 } 670 671 func (se *SessionExecutor) isInTransaction() bool { 672 return se.status&mysql.ServerStatusInTrans > 0 || 673 !se.isAutoCommit() 674 } 675 676 func (se *SessionExecutor) isAutoCommit() bool { 677 return se.status&mysql.ServerStatusAutocommit > 0 678 } 679 680 func (se *SessionExecutor) handleBegin() error { 681 se.txLock.Lock() 682 defer se.txLock.Unlock() 683 684 for _, co := range se.txConns { 685 if err := co.Begin(); err != nil { 686 return err 687 } 688 } 689 se.status |= mysql.ServerStatusInTrans 690 se.savepoints = []string{} 691 return nil 692 } 693 694 func (se *SessionExecutor) handleCommit() (err error) { 695 if err := se.commit(); err != nil { 696 return err 697 } 698 return nil 699 700 } 701 702 func (se *SessionExecutor) handleRollback(stmt *ast.RollbackStmt) (err error) { 703 if stmt == nil || stmt.Savepoint == "" { 704 return se.rollback() 705 } else { 706 return se.rollbackSavepoint(stmt.Savepoint) 707 } 708 } 709 710 func (se *SessionExecutor) commit() (err error) { 711 se.txLock.Lock() 712 defer se.txLock.Unlock() 713 714 se.status &= ^mysql.ServerStatusInTrans 715 716 for _, pc := range se.txConns { 717 if e := pc.Commit(); e != nil { 718 err = e 719 } 720 pc.Recycle() 721 } 722 723 se.txConns = make(map[string]backend.PooledConnect) 724 se.savepoints = []string{} 725 return 726 } 727 728 func (se *SessionExecutor) rollback() (err error) { 729 se.txLock.Lock() 730 defer se.txLock.Unlock() 731 se.status &= ^mysql.ServerStatusInTrans 732 for _, pc := range se.txConns { 733 err = pc.Rollback() 734 pc.Recycle() 735 } 736 se.txConns = make(map[string]backend.PooledConnect) 737 se.savepoints = []string{} 738 return 739 } 740 741 func (se *SessionExecutor) rollbackSavepoint(savepoint string) (err error) { 742 se.txLock.Lock() 743 defer se.txLock.Unlock() 744 for _, pc := range se.txConns { 745 _, err = pc.Execute("rollback to "+savepoint, 0) 746 } 747 if err == nil && se.isInTransaction() { 748 if index := util.ArrayFindIndex(se.savepoints, savepoint); index > -1 { 749 se.savepoints = se.savepoints[0:index] 750 } 751 } 752 return 753 } 754 755 func (se *SessionExecutor) handleSavepoint(stmt *ast.SavepointStmt) (err error) { 756 se.txLock.Lock() 757 defer se.txLock.Unlock() 758 if stmt.Release { 759 for _, pc := range se.txConns { 760 _, err = pc.Execute("release savepoint "+stmt.Savepoint, 0) 761 } 762 if err == nil && se.isInTransaction() { 763 if index := util.ArrayFindIndex(se.savepoints, stmt.Savepoint); index > -1 { 764 se.savepoints = se.savepoints[0 : index+1] 765 } 766 } 767 } else { 768 for _, pc := range se.txConns { 769 _, err = pc.Execute("savepoint "+stmt.Savepoint, 0) 770 } 771 if err == nil && se.isInTransaction() { 772 if util.ArrayFindIndex(se.savepoints, stmt.Savepoint) > -1 { 773 se.savepoints = util.ArrayRemoveItem(se.savepoints, stmt.Savepoint) 774 } 775 se.savepoints = append(se.savepoints, stmt.Savepoint) 776 } 777 } 778 return 779 } 780 781 // ExecuteSQL execute sql 782 func (se *SessionExecutor) ExecuteSQL(reqCtx *util.RequestContext, slice, db, sql string) (*mysql.Result, error) { 783 phyDB, err := se.GetNamespace().GetDefaultPhyDB(db) 784 if err != nil { 785 return nil, err 786 } 787 788 sqls := make(map[string]map[string][]string) 789 dbSQLs := make(map[string][]string) 790 dbSQLs[phyDB] = []string{sql} 791 sqls[slice] = dbSQLs 792 793 pcs, err := se.getBackendConns(sqls, getFromSlave(reqCtx)) 794 defer se.recycleBackendConns(pcs, false) 795 if err != nil { 796 log.Warn("getUnShardConns failed: %v", err) 797 return nil, err 798 } 799 800 rs, err := se.executeInMultiSlices(reqCtx, pcs, sqls) 801 if err != nil { 802 return nil, err 803 } 804 805 if len(rs) == 0 { 806 return nil, mysql.NewError(mysql.ErrUnknown, "result is empty") 807 } 808 return rs[0], nil 809 } 810 811 // ExecuteSQLs len(sqls) must not be 0, or return error 812 func (se *SessionExecutor) ExecuteSQLs(reqCtx *util.RequestContext, sqls map[string]map[string][]string) ([]*mysql.Result, error) { 813 if len(sqls) == 0 { 814 return nil, fmt.Errorf("no sql to execute") 815 } 816 817 pcs, err := se.getBackendConns(sqls, getFromSlave(reqCtx)) 818 defer se.recycleBackendConns(pcs, false) 819 if err != nil { 820 log.Warn("getShardConns failed: %v", err) 821 return nil, err 822 } 823 824 rs, err := se.executeInMultiSlices(reqCtx, pcs, sqls) 825 if err != nil { 826 return nil, err 827 } 828 return rs, nil 829 }