github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/session.go (about) 1 // Copyright 2013 The ql Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSES/QL-LICENSE file. 4 5 // Copyright 2015 PingCAP, Inc. 6 // 7 // Licensed under the Apache License, Version 2.0 (the "License"); 8 // you may not use this file except in compliance with the License. 9 // You may obtain a copy of the License at 10 // 11 // http://www.apache.org/licenses/LICENSE-2.0 12 // 13 // Unless required by applicable law or agreed to in writing, software 14 // distributed under the License is distributed on an "AS IS" BASIS, 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package tidb 19 20 import ( 21 "bytes" 22 "encoding/json" 23 "fmt" 24 "strings" 25 "sync" 26 "sync/atomic" 27 "time" 28 29 "github.com/insionng/yougam/libraries/juju/errors" 30 "github.com/insionng/yougam/libraries/ngaut/log" 31 "github.com/insionng/yougam/libraries/pingcap/tidb/ast" 32 "github.com/insionng/yougam/libraries/pingcap/tidb/context" 33 "github.com/insionng/yougam/libraries/pingcap/tidb/executor" 34 "github.com/insionng/yougam/libraries/pingcap/tidb/kv" 35 "github.com/insionng/yougam/libraries/pingcap/tidb/meta" 36 "github.com/insionng/yougam/libraries/pingcap/tidb/mysql" 37 "github.com/insionng/yougam/libraries/pingcap/tidb/perfschema" 38 "github.com/insionng/yougam/libraries/pingcap/tidb/privilege" 39 "github.com/insionng/yougam/libraries/pingcap/tidb/privilege/privileges" 40 "github.com/insionng/yougam/libraries/pingcap/tidb/sessionctx" 41 "github.com/insionng/yougam/libraries/pingcap/tidb/sessionctx/autocommit" 42 "github.com/insionng/yougam/libraries/pingcap/tidb/sessionctx/db" 43 "github.com/insionng/yougam/libraries/pingcap/tidb/sessionctx/forupdate" 44 "github.com/insionng/yougam/libraries/pingcap/tidb/sessionctx/variable" 45 "github.com/insionng/yougam/libraries/pingcap/tidb/store/localstore" 46 "github.com/insionng/yougam/libraries/pingcap/tidb/terror" 47 "github.com/insionng/yougam/libraries/pingcap/tidb/util" 48 "github.com/insionng/yougam/libraries/pingcap/tidb/util/types" 49 ) 50 51 // Session context 52 type Session interface { 53 Status() uint16 // Flag of current status, such as autocommit 54 LastInsertID() uint64 // Last inserted auto_increment id 55 AffectedRows() uint64 // Affected rows by latest executed stmt 56 Execute(sql string) ([]ast.RecordSet, error) // Execute a sql statement 57 String() string // For debug 58 FinishTxn(rollback bool) error 59 // For execute prepare statement in binary protocol 60 PrepareStmt(sql string) (stmtID uint32, paramCount int, fields []*ast.ResultField, err error) 61 // Execute a prepared statement 62 ExecutePreparedStmt(stmtID uint32, param ...interface{}) (ast.RecordSet, error) 63 DropPreparedStmt(stmtID uint32) error 64 SetClientCapability(uint32) // Set client capability flags 65 SetConnectionID(uint64) 66 Close() error 67 Retry() error 68 Auth(user string, auth []byte, salt []byte) bool 69 } 70 71 var ( 72 _ Session = (*session)(nil) 73 sessionID int64 74 sessionMu sync.Mutex 75 ) 76 77 type stmtRecord struct { 78 stmtID uint32 79 st ast.Statement 80 params []interface{} 81 } 82 83 type stmtHistory struct { 84 history []*stmtRecord 85 } 86 87 func (h *stmtHistory) add(stmtID uint32, st ast.Statement, params ...interface{}) { 88 s := &stmtRecord{ 89 stmtID: stmtID, 90 st: st, 91 params: append(([]interface{})(nil), params...), 92 } 93 h.history = append(h.history, s) 94 } 95 96 func (h *stmtHistory) reset() { 97 if len(h.history) > 0 { 98 h.history = h.history[:0] 99 } 100 } 101 102 func (h *stmtHistory) clone() *stmtHistory { 103 nh := *h 104 nh.history = make([]*stmtRecord, len(h.history)) 105 copy(nh.history, h.history) 106 return &nh 107 } 108 109 const unlimitedRetryCnt = -1 110 111 type session struct { 112 txn kv.Transaction // Current transaction 113 values map[fmt.Stringer]interface{} 114 store kv.Storage 115 sid int64 116 history stmtHistory 117 initing bool // Running bootstrap using this session. 118 maxRetryCnt int // Max retry times. If maxRetryCnt <=0, there is no limitation for retry times. 119 120 debugInfos map[string]interface{} // Vars for debug and unit tests. 121 122 // For performance_schema only. 123 stmtState *perfschema.StatementState 124 } 125 126 func (s *session) cleanRetryInfo() { 127 if !variable.GetSessionVars(s).RetryInfo.Retrying { 128 variable.GetSessionVars(s).RetryInfo.Clean() 129 } 130 } 131 132 func (s *session) Status() uint16 { 133 return variable.GetSessionVars(s).Status 134 } 135 136 func (s *session) LastInsertID() uint64 { 137 return variable.GetSessionVars(s).LastInsertID 138 } 139 140 func (s *session) AffectedRows() uint64 { 141 return variable.GetSessionVars(s).AffectedRows 142 } 143 144 func (s *session) resetHistory() { 145 s.ClearValue(forupdate.ForUpdateKey) 146 s.history.reset() 147 } 148 149 func (s *session) SetClientCapability(capability uint32) { 150 variable.GetSessionVars(s).ClientCapability = capability 151 } 152 153 func (s *session) SetConnectionID(connectionID uint64) { 154 variable.GetSessionVars(s).ConnectionID = connectionID 155 } 156 157 func (s *session) FinishTxn(rollback bool) error { 158 // transaction has already been committed or rolled back 159 if s.txn == nil { 160 return nil 161 } 162 defer func() { 163 s.txn = nil 164 variable.GetSessionVars(s).SetStatusFlag(mysql.ServerStatusInTrans, false) 165 // Update tps metrics 166 if !variable.GetSessionVars(s).RetryInfo.Retrying { 167 tpsMetrics.Add(1) 168 } 169 170 }() 171 172 if rollback { 173 s.resetHistory() 174 s.cleanRetryInfo() 175 return s.txn.Rollback() 176 } 177 178 err := s.txn.Commit() 179 if err != nil { 180 if !variable.GetSessionVars(s).RetryInfo.Retrying && kv.IsRetryableError(err) { 181 err = s.Retry() 182 } 183 if err != nil { 184 log.Warnf("txn:%s, %v", s.txn, err) 185 return errors.Trace(err) 186 } 187 } 188 189 s.resetHistory() 190 s.cleanRetryInfo() 191 return nil 192 } 193 194 func (s *session) String() string { 195 // TODO: how to print binded context in values appropriately? 196 data := map[string]interface{}{ 197 "currDBName": db.GetCurrentSchema(s), 198 "sid": s.sid, 199 } 200 201 if s.txn != nil { 202 // if txn is committed or rolled back, txn is nil. 203 data["txn"] = s.txn.String() 204 } 205 206 b, _ := json.MarshalIndent(data, "", " ") 207 return string(b) 208 } 209 210 func (s *session) Retry() error { 211 variable.GetSessionVars(s).RetryInfo.Retrying = true 212 nh := s.history.clone() 213 // Debug infos. 214 if len(nh.history) == 0 { 215 s.debugInfos[retryEmptyHistoryList] = true 216 } else { 217 s.debugInfos[retryEmptyHistoryList] = false 218 } 219 defer func() { 220 s.history.history = nh.history 221 variable.GetSessionVars(s).RetryInfo.Retrying = false 222 }() 223 224 if forUpdate := s.Value(forupdate.ForUpdateKey); forUpdate != nil { 225 return errors.Errorf("can not retry select for update statement") 226 } 227 var err error 228 retryCnt := 0 229 for { 230 s.resetHistory() 231 s.FinishTxn(true) 232 success := true 233 variable.GetSessionVars(s).RetryInfo.ResetOffset() 234 for _, sr := range nh.history { 235 st := sr.st 236 log.Warnf("Retry %s", st.OriginText()) 237 _, err = runStmt(s, st) 238 if err != nil { 239 if kv.IsRetryableError(err) { 240 success = false 241 break 242 } 243 log.Warnf("session:%v, err:%v", s, err) 244 return errors.Trace(err) 245 } 246 } 247 if success { 248 err = s.FinishTxn(false) 249 if !kv.IsRetryableError(err) { 250 break 251 } 252 } 253 retryCnt++ 254 if (s.maxRetryCnt != unlimitedRetryCnt) && (retryCnt >= s.maxRetryCnt) { 255 return errors.Trace(err) 256 } 257 kv.BackOff(retryCnt) 258 } 259 return err 260 } 261 262 // ExecRestrictedSQL implements SQLHelper interface. 263 // This is used for executing some restricted sql statements. 264 func (s *session) ExecRestrictedSQL(ctx context.Context, sql string) (ast.RecordSet, error) { 265 rawStmts, err := Parse(ctx, sql) 266 if err != nil { 267 return nil, errors.Trace(err) 268 } 269 if len(rawStmts) != 1 { 270 log.Errorf("ExecRestrictedSQL only executes one statement. Too many/few statement in %s", sql) 271 return nil, errors.New("Wrong number of statement.") 272 } 273 st, err := Compile(s, rawStmts[0]) 274 if err != nil { 275 log.Errorf("Compile %s with error: %v", sql, err) 276 return nil, errors.Trace(err) 277 } 278 // Check statement for some restriction 279 // For example only support DML on system meta table. 280 // TODO: Add more restrictions. 281 log.Debugf("Executing %s [%s]", st.OriginText(), sql) 282 rs, err := st.Exec(ctx) 283 return rs, errors.Trace(err) 284 } 285 286 // getExecRet executes restricted sql and the result is one column. 287 // It returns a string value. 288 func (s *session) getExecRet(ctx context.Context, sql string) (string, error) { 289 cleanTxn := s.txn == nil 290 rs, err := s.ExecRestrictedSQL(ctx, sql) 291 if err != nil { 292 return "", errors.Trace(err) 293 } 294 defer rs.Close() 295 row, err := rs.Next() 296 if err != nil { 297 return "", errors.Trace(err) 298 } 299 if row == nil { 300 return "", terror.ExecResultIsEmpty 301 } 302 value, err := types.ToString(row.Data[0].GetValue()) 303 if err != nil { 304 return "", errors.Trace(err) 305 } 306 if cleanTxn { 307 // This function has some side effect. Run select may create new txn. 308 // We should make environment unchanged. 309 s.txn = nil 310 } 311 return value, nil 312 } 313 314 // GetGlobalSysVar implements GlobalVarAccessor.GetGlobalSysVar interface. 315 func (s *session) GetGlobalSysVar(ctx context.Context, name string) (string, error) { 316 sql := fmt.Sprintf(`SELECT VARIABLE_VALUE FROM %s.%s WHERE VARIABLE_NAME="%s";`, 317 mysql.SystemDB, mysql.GlobalVariablesTable, name) 318 sysVar, err := s.getExecRet(ctx, sql) 319 if err != nil { 320 if terror.ExecResultIsEmpty.Equal(err) { 321 return "", variable.UnknownSystemVar.Gen("unknown sys variable:%s", name) 322 } 323 return "", errors.Trace(err) 324 } 325 return sysVar, nil 326 } 327 328 // SetGlobalSysVar implements GlobalVarAccessor.SetGlobalSysVar interface. 329 func (s *session) SetGlobalSysVar(ctx context.Context, name string, value string) error { 330 sql := fmt.Sprintf(`UPDATE %s.%s SET VARIABLE_VALUE="%s" WHERE VARIABLE_NAME="%s";`, 331 mysql.SystemDB, mysql.GlobalVariablesTable, value, strings.ToLower(name)) 332 _, err := s.ExecRestrictedSQL(ctx, sql) 333 return errors.Trace(err) 334 } 335 336 // IsAutocommit checks if it is in the auto-commit mode. 337 func (s *session) isAutocommit(ctx context.Context) bool { 338 autocommit, ok := variable.GetSessionVars(ctx).Systems["autocommit"] 339 if !ok { 340 if s.initing { 341 return false 342 } 343 var err error 344 autocommit, err = s.GetGlobalSysVar(ctx, "autocommit") 345 if err != nil { 346 log.Errorf("Get global sys var error: %v", err) 347 return false 348 } 349 variable.GetSessionVars(ctx).Systems["autocommit"] = autocommit 350 ok = true 351 } 352 if ok && (autocommit == "ON" || autocommit == "on" || autocommit == "1") { 353 variable.GetSessionVars(ctx).SetStatusFlag(mysql.ServerStatusAutocommit, true) 354 return true 355 } 356 variable.GetSessionVars(ctx).SetStatusFlag(mysql.ServerStatusAutocommit, false) 357 return false 358 } 359 360 func (s *session) ShouldAutocommit(ctx context.Context) bool { 361 // With START TRANSACTION, autocommit remains disabled until you end 362 // the transaction with COMMIT or ROLLBACK. 363 if variable.GetSessionVars(ctx).Status&mysql.ServerStatusInTrans == 0 && s.isAutocommit(ctx) { 364 return true 365 } 366 return false 367 } 368 369 func (s *session) Execute(sql string) ([]ast.RecordSet, error) { 370 rawStmts, err := Parse(s, sql) 371 if err != nil { 372 return nil, errors.Trace(err) 373 } 374 var rs []ast.RecordSet 375 ph := sessionctx.GetDomain(s).PerfSchema() 376 for i, rst := range rawStmts { 377 st, err1 := Compile(s, rst) 378 if err1 != nil { 379 log.Errorf("Syntax error: %s", sql) 380 log.Errorf("Error occurs at %s.", err1) 381 return nil, errors.Trace(err1) 382 } 383 id := variable.GetSessionVars(s).ConnectionID 384 s.stmtState = ph.StartStatement(sql, id, perfschema.CallerNameSessionExecute, rawStmts[i]) 385 r, err := runStmt(s, st) 386 ph.EndStatement(s.stmtState) 387 if err != nil { 388 log.Warnf("session:%v, err:%v", s, err) 389 return nil, errors.Trace(err) 390 } 391 if r != nil { 392 rs = append(rs, r) 393 } 394 } 395 return rs, nil 396 } 397 398 // For execute prepare statement in binary protocol 399 func (s *session) PrepareStmt(sql string) (stmtID uint32, paramCount int, fields []*ast.ResultField, err error) { 400 prepareExec := &executor.PrepareExec{ 401 IS: sessionctx.GetDomain(s).InfoSchema(), 402 Ctx: s, 403 SQLText: sql, 404 } 405 prepareExec.DoPrepare() 406 return prepareExec.ID, prepareExec.ParamCount, prepareExec.ResultFields, prepareExec.Err 407 } 408 409 // checkArgs makes sure all the arguments' types are known and can be handled. 410 // integer types are converted to int64 and uint64, time.Time is converted to mysql.Time. 411 // time.Duration is converted to mysql.Duration, other known types are leaved as it is. 412 func checkArgs(args ...interface{}) error { 413 for i, v := range args { 414 switch x := v.(type) { 415 case bool: 416 if x { 417 args[i] = int64(1) 418 } else { 419 args[i] = int64(0) 420 } 421 case int8: 422 args[i] = int64(x) 423 case int16: 424 args[i] = int64(x) 425 case int32: 426 args[i] = int64(x) 427 case int: 428 args[i] = int64(x) 429 case uint8: 430 args[i] = uint64(x) 431 case uint16: 432 args[i] = uint64(x) 433 case uint32: 434 args[i] = uint64(x) 435 case uint: 436 args[i] = uint64(x) 437 case int64: 438 case uint64: 439 case float32: 440 case float64: 441 case string: 442 case []byte: 443 case time.Duration: 444 args[i] = mysql.Duration{Duration: x} 445 case time.Time: 446 args[i] = mysql.Time{Time: x, Type: mysql.TypeDatetime} 447 case nil: 448 default: 449 return errors.Errorf("cannot use arg[%d] (type %T):unsupported type", i, v) 450 } 451 } 452 return nil 453 } 454 455 // Execute a prepared statement 456 func (s *session) ExecutePreparedStmt(stmtID uint32, args ...interface{}) (ast.RecordSet, error) { 457 err := checkArgs(args...) 458 if err != nil { 459 return nil, err 460 } 461 st := executor.CompileExecutePreparedStmt(s, stmtID, args...) 462 r, err := runStmt(s, st, args...) 463 return r, errors.Trace(err) 464 } 465 466 func (s *session) DropPreparedStmt(stmtID uint32) error { 467 vars := variable.GetSessionVars(s) 468 if _, ok := vars.PreparedStmts[stmtID]; !ok { 469 return executor.ErrStmtNotFound 470 } 471 delete(vars.PreparedStmts, stmtID) 472 return nil 473 } 474 475 // If forceNew is true, GetTxn() must return a new transaction. 476 // In this situation, if current transaction is still in progress, 477 // there will be an implicit commit and create a new transaction. 478 func (s *session) GetTxn(forceNew bool) (kv.Transaction, error) { 479 var err error 480 if s.txn == nil { 481 s.resetHistory() 482 s.txn, err = s.store.Begin() 483 if err != nil { 484 return nil, errors.Trace(err) 485 } 486 if !s.isAutocommit(s) { 487 variable.GetSessionVars(s).SetStatusFlag(mysql.ServerStatusInTrans, true) 488 } 489 log.Infof("New txn:%s in session:%d", s.txn, s.sid) 490 return s.txn, nil 491 } 492 if forceNew { 493 err = s.FinishTxn(false) 494 if err != nil { 495 return nil, errors.Trace(err) 496 } 497 s.txn, err = s.store.Begin() 498 if err != nil { 499 return nil, errors.Trace(err) 500 } 501 if !s.isAutocommit(s) { 502 variable.GetSessionVars(s).SetStatusFlag(mysql.ServerStatusInTrans, true) 503 } 504 log.Warnf("Force new txn:%s in session:%d", s.txn, s.sid) 505 } 506 return s.txn, nil 507 } 508 509 func (s *session) SetValue(key fmt.Stringer, value interface{}) { 510 s.values[key] = value 511 } 512 513 func (s *session) Value(key fmt.Stringer) interface{} { 514 value := s.values[key] 515 return value 516 } 517 518 func (s *session) ClearValue(key fmt.Stringer) { 519 delete(s.values, key) 520 } 521 522 // Close function does some clean work when session end. 523 func (s *session) Close() error { 524 return s.FinishTxn(true) 525 } 526 527 func (s *session) getPassword(name, host string) (string, error) { 528 // Get password for name and host. 529 authSQL := fmt.Sprintf("SELECT Password FROM %s.%s WHERE User='%s' and Host='%s';", mysql.SystemDB, mysql.UserTable, name, host) 530 pwd, err := s.getExecRet(s, authSQL) 531 if err == nil { 532 return pwd, nil 533 } else if !terror.ExecResultIsEmpty.Equal(err) { 534 return "", errors.Trace(err) 535 } 536 //Try to get user password for name with any host(%). 537 authSQL = fmt.Sprintf("SELECT Password FROM %s.%s WHERE User='%s' and Host='%%';", mysql.SystemDB, mysql.UserTable, name) 538 pwd, err = s.getExecRet(s, authSQL) 539 return pwd, errors.Trace(err) 540 } 541 542 func (s *session) Auth(user string, auth []byte, salt []byte) bool { 543 strs := strings.Split(user, "@") 544 if len(strs) != 2 { 545 log.Warnf("Invalid format for user: %s", user) 546 return false 547 } 548 // Get user password. 549 name := strs[0] 550 host := strs[1] 551 pwd, err := s.getPassword(name, host) 552 if err != nil { 553 if terror.ExecResultIsEmpty.Equal(err) { 554 log.Errorf("User [%s] not exist %v", name, err) 555 } else { 556 log.Errorf("Get User [%s] password from SystemDB error %v", name, err) 557 } 558 return false 559 } 560 if len(pwd) != 0 && len(pwd) != 40 { 561 log.Errorf("User [%s] password from SystemDB not like a sha1sum", name) 562 return false 563 } 564 hpwd, err := util.DecodePassword(pwd) 565 if err != nil { 566 log.Errorf("Decode password string error %v", err) 567 return false 568 } 569 checkAuth := util.CalcPassword(salt, hpwd) 570 if !bytes.Equal(auth, checkAuth) { 571 return false 572 } 573 variable.GetSessionVars(s).SetCurrentUser(user) 574 return true 575 } 576 577 // Some vars name for debug. 578 const ( 579 retryEmptyHistoryList = "RetryEmptyHistoryList" 580 ) 581 582 func chooseMinLease(n1 time.Duration, n2 time.Duration) time.Duration { 583 if n1 <= n2 { 584 return n1 585 } 586 return n2 587 } 588 589 // CreateSession creates a new session environment. 590 func CreateSession(store kv.Storage) (Session, error) { 591 s := &session{ 592 values: make(map[fmt.Stringer]interface{}), 593 store: store, 594 sid: atomic.AddInt64(&sessionID, 1), 595 debugInfos: make(map[string]interface{}), 596 maxRetryCnt: 10, 597 } 598 domain, err := domap.Get(store) 599 if err != nil { 600 return nil, err 601 } 602 sessionctx.BindDomain(s, domain) 603 604 variable.BindSessionVars(s) 605 variable.GetSessionVars(s).SetStatusFlag(mysql.ServerStatusAutocommit, true) 606 607 // session implements variable.GlobalVarAccessor. Bind it to ctx. 608 variable.BindGlobalVarAccessor(s, s) 609 610 // session implements autocommit.Checker. Bind it to ctx 611 autocommit.BindAutocommitChecker(s, s) 612 sessionMu.Lock() 613 defer sessionMu.Unlock() 614 615 ok := isBoostrapped(store) 616 if !ok { 617 // if no bootstrap and storage is remote, we must use a little lease time to 618 // bootstrap quickly, after bootstrapped, we will reset the lease time. 619 // TODO: Using a bootstap tool for doing this may be better later. 620 if !localstore.IsLocalStore(store) { 621 sessionctx.GetDomain(s).SetLease(chooseMinLease(100*time.Millisecond, schemaLease)) 622 } 623 624 s.initing = true 625 bootstrap(s) 626 s.initing = false 627 628 if !localstore.IsLocalStore(store) { 629 sessionctx.GetDomain(s).SetLease(schemaLease) 630 } 631 632 finishBoostrap(store) 633 } 634 635 // TODO: Add auth here 636 privChecker := &privileges.UserPrivileges{} 637 privilege.BindPrivilegeChecker(s, privChecker) 638 return s, nil 639 } 640 641 func isBoostrapped(store kv.Storage) bool { 642 // check in memory 643 _, ok := storeBootstrapped[store.UUID()] 644 if ok { 645 return true 646 } 647 648 // check in kv store 649 err := kv.RunInNewTxn(store, false, func(txn kv.Transaction) error { 650 var err error 651 t := meta.NewMeta(txn) 652 ok, err = t.IsBootstrapped() 653 return errors.Trace(err) 654 }) 655 656 if err != nil { 657 log.Fatalf("check bootstrapped err %v", err) 658 } 659 660 if ok { 661 // here mean memory is not ok, but other server has already finished it 662 storeBootstrapped[store.UUID()] = true 663 } 664 665 return ok 666 } 667 668 func finishBoostrap(store kv.Storage) { 669 storeBootstrapped[store.UUID()] = true 670 671 err := kv.RunInNewTxn(store, true, func(txn kv.Transaction) error { 672 t := meta.NewMeta(txn) 673 err := t.FinishBootstrap() 674 return errors.Trace(err) 675 }) 676 if err != nil { 677 log.Fatalf("finish bootstrap err %v", err) 678 } 679 }