vitess.io/vitess@v0.16.2/go/vt/vtgate/safe_session.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package vtgate
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"google.golang.org/protobuf/proto"
    27  
    28  	"vitess.io/vitess/go/vt/sqlparser"
    29  	"vitess.io/vitess/go/vt/srvtopo"
    30  	"vitess.io/vitess/go/vt/sysvars"
    31  	"vitess.io/vitess/go/vt/vterrors"
    32  	"vitess.io/vitess/go/vt/vtgate/engine"
    33  
    34  	querypb "vitess.io/vitess/go/vt/proto/query"
    35  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    36  	vtgatepb "vitess.io/vitess/go/vt/proto/vtgate"
    37  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    38  )
    39  
    40  type (
    41  	// SafeSession is a mutex-protected version of the Session.
    42  	// It is thread-safe if each thread only accesses one shard.
    43  	// (the use pattern is 'Find', if not found, then 'AppendOrUpdate',
    44  	// for a single shard)
    45  	SafeSession struct {
    46  		mu              sync.Mutex
    47  		mustRollback    bool
    48  		autocommitState autocommitState
    49  		commitOrder     vtgatepb.CommitOrder
    50  		savepointState  savepointState
    51  		// rollbackOnPartialExec is set if any DML was successfully
    52  		// executed. If there was a subsequent failure, if we have a savepoint we rollback to that.
    53  		// Otherwise, the transaction is rolled back.
    54  		rollbackOnPartialExec string
    55  		savepointName         string
    56  
    57  		// this is a signal that found_rows has already been handles by the primitives,
    58  		// and doesn't have to be updated by the executor
    59  		foundRowsHandled bool
    60  
    61  		// queryFromVindex is used to avoid erroring out on multi-db transaction
    62  		// as the query that started a new transaction on the shard belong to a vindex.
    63  		queryFromVindex bool
    64  
    65  		logging *executeLogger
    66  
    67  		*vtgatepb.Session
    68  	}
    69  
    70  	executeLogger struct {
    71  		mu      sync.Mutex
    72  		entries []engine.ExecuteEntry
    73  		lastID  int
    74  	}
    75  
    76  	// autocommitState keeps track of whether a single round-trip
    77  	// commit to vttablet is possible. It starts as autocommitable
    78  	// if we started a transaction because of the autocommit flag
    79  	// being set. Otherwise, it starts as notAutocommitable.
    80  	// If execute is recursively called using the same session,
    81  	// like from a vindex, we will already be in a transaction,
    82  	// and this should cause the state to become notAutocommitable.
    83  	//
    84  	// SafeSession lets you request a commit token, which will
    85  	// be issued if the state is autocommitable,
    86  	// implying that no intermediate transactions were started.
    87  	// If so, the state transitions to autocommited, which is terminal.
    88  	// If the token is successfully issued, the caller has to perform
    89  	// the commit. If a token cannot be issued, then a traditional
    90  	// commit has to be performed at the outermost level where
    91  	// the autocommitable transition happened.
    92  	autocommitState int
    93  
    94  	// savepointState keeps track of whether savepoints need to be inserted
    95  	// before running the query. This will help us prevent rolling back the
    96  	// entire transaction in case of partial failures, and be closer to MySQL
    97  	// compatibility, by only reverting the changes from the failed statement
    98  	// If execute is recursively called using the same session,
    99  	// like from a vindex, we should not override the savePointState.
   100  	// It is set the first time and is then permanent for the remainder of the query
   101  	// execution. It should not be affected later by transactions starting or not.
   102  	savepointState int
   103  )
   104  
   105  const (
   106  	notAutocommittable = autocommitState(iota)
   107  	autocommittable
   108  	autocommitted
   109  )
   110  
   111  const (
   112  	savepointStateNotSet = savepointState(iota)
   113  	// savepointNotNeeded - savepoint is not required
   114  	savepointNotNeeded
   115  	// savepointNeeded - savepoint may be required
   116  	savepointNeeded
   117  	// savepointSet - savepoint is set on the session
   118  	savepointSet
   119  	// savepointRollbackSet - rollback to savepoint is set on the session
   120  	savepointRollbackSet
   121  	// savepointRollback - rollback happened on the savepoint
   122  	savepointRollback
   123  )
   124  
   125  // NewSafeSession returns a new SafeSession based on the Session
   126  func NewSafeSession(sessn *vtgatepb.Session) *SafeSession {
   127  	if sessn == nil {
   128  		sessn = &vtgatepb.Session{}
   129  	}
   130  	return &SafeSession{Session: sessn}
   131  }
   132  
   133  // NewAutocommitSession returns a SafeSession based on the original
   134  // session, but with autocommit enabled.
   135  func NewAutocommitSession(sessn *vtgatepb.Session) *SafeSession {
   136  	newSession := proto.Clone(sessn).(*vtgatepb.Session)
   137  	newSession.InTransaction = false
   138  	newSession.ShardSessions = nil
   139  	newSession.PreSessions = nil
   140  	newSession.PostSessions = nil
   141  	newSession.LockSession = nil
   142  	newSession.Autocommit = true
   143  	newSession.Warnings = nil
   144  	return NewSafeSession(newSession)
   145  }
   146  
   147  // ResetTx clears the session
   148  func (session *SafeSession) ResetTx() {
   149  	session.mu.Lock()
   150  	defer session.mu.Unlock()
   151  	session.resetCommonLocked()
   152  	// If settings pools is enabled on the vttablet.
   153  	// This variable will be true but there will not be a shard session with reserved connection id.
   154  	// So, we should check the shard session and not just this variable.
   155  	if session.Session.InReservedConn {
   156  		allSessions := append(session.ShardSessions, append(session.PreSessions, session.PostSessions...)...)
   157  		for _, ss := range allSessions {
   158  			if ss.ReservedId != 0 {
   159  				// found that reserved connection exists.
   160  				// abort here, we should keep the shard sessions.
   161  				return
   162  			}
   163  		}
   164  	}
   165  	session.ShardSessions = nil
   166  	session.PreSessions = nil
   167  	session.PostSessions = nil
   168  }
   169  
   170  // Reset clears the session
   171  func (session *SafeSession) Reset() {
   172  	session.mu.Lock()
   173  	defer session.mu.Unlock()
   174  	session.resetCommonLocked()
   175  	session.ShardSessions = nil
   176  	session.PreSessions = nil
   177  	session.PostSessions = nil
   178  }
   179  
   180  // ResetAll resets the shard sessions and lock session.
   181  func (session *SafeSession) ResetAll() {
   182  	session.mu.Lock()
   183  	defer session.mu.Unlock()
   184  	session.resetCommonLocked()
   185  	session.ShardSessions = nil
   186  	session.PreSessions = nil
   187  	session.PostSessions = nil
   188  	session.LockSession = nil
   189  	session.AdvisoryLock = nil
   190  }
   191  
   192  func (session *SafeSession) resetCommonLocked() {
   193  	session.mustRollback = false
   194  	session.autocommitState = notAutocommittable
   195  	session.Session.InTransaction = false
   196  	session.commitOrder = vtgatepb.CommitOrder_NORMAL
   197  	session.Savepoints = nil
   198  	if session.Options != nil {
   199  		session.Options.TransactionAccessMode = nil
   200  	}
   201  }
   202  
   203  // SetQueryTimeout sets the query timeout
   204  func (session *SafeSession) SetQueryTimeout(queryTimeout int64) {
   205  	session.mu.Lock()
   206  	defer session.mu.Unlock()
   207  	session.QueryTimeout = queryTimeout
   208  }
   209  
   210  // GetQueryTimeout gets the query timeout
   211  func (session *SafeSession) GetQueryTimeout() int64 {
   212  	session.mu.Lock()
   213  	defer session.mu.Unlock()
   214  	return session.QueryTimeout
   215  }
   216  
   217  // SavePoints returns the save points of the session. It's safe to use concurrently
   218  func (session *SafeSession) SavePoints() []string {
   219  	session.mu.Lock()
   220  	defer session.mu.Unlock()
   221  	return session.GetSavepoints()
   222  }
   223  
   224  // SetAutocommittable sets the state to autocommitable if true.
   225  // Otherwise, it's notAutocommitable.
   226  func (session *SafeSession) SetAutocommittable(flag bool) {
   227  	session.mu.Lock()
   228  	defer session.mu.Unlock()
   229  
   230  	if session.autocommitState == autocommitted {
   231  		// Unreachable.
   232  		return
   233  	}
   234  
   235  	if flag {
   236  		session.autocommitState = autocommittable
   237  	} else {
   238  		session.autocommitState = notAutocommittable
   239  	}
   240  }
   241  
   242  // AutocommitApproval returns true if we can perform a single round-trip
   243  // autocommit. If so, the caller is responsible for committing their
   244  // transaction.
   245  func (session *SafeSession) AutocommitApproval() bool {
   246  	session.mu.Lock()
   247  	defer session.mu.Unlock()
   248  
   249  	if session.autocommitState == autocommitted {
   250  		// Unreachable.
   251  		return false
   252  	}
   253  
   254  	if session.autocommitState == autocommittable {
   255  		session.autocommitState = autocommitted
   256  		return true
   257  	}
   258  	return false
   259  }
   260  
   261  // SetSavepointState sets the state only once for the complete query execution life.
   262  // Calling the function multiple times will have no effect, only the first call would be used.
   263  // Default state is savepointStateNotSet,
   264  // if savepoint needed (spNeed true) then it will be set to savepointNeeded otherwise savepointNotNeeded.
   265  func (session *SafeSession) SetSavepointState(spNeed bool) {
   266  	session.mu.Lock()
   267  	defer session.mu.Unlock()
   268  
   269  	if session.savepointState != savepointStateNotSet {
   270  		return
   271  	}
   272  
   273  	if spNeed {
   274  		session.savepointState = savepointNeeded
   275  	} else {
   276  		session.savepointState = savepointNotNeeded
   277  	}
   278  }
   279  
   280  // CanAddSavepoint returns true if we should insert savepoint and there is no existing savepoint.
   281  func (session *SafeSession) CanAddSavepoint() bool {
   282  	session.mu.Lock()
   283  	defer session.mu.Unlock()
   284  
   285  	return session.savepointState == savepointNeeded
   286  }
   287  
   288  // SetSavepoint stores the savepoint name to session.
   289  func (session *SafeSession) SetSavepoint(name string) {
   290  	session.mu.Lock()
   291  	defer session.mu.Unlock()
   292  
   293  	session.savepointName = name
   294  	session.savepointState = savepointSet
   295  }
   296  
   297  // SetRollbackCommand stores the rollback command to session and executed if required.
   298  func (session *SafeSession) SetRollbackCommand() {
   299  	session.mu.Lock()
   300  	defer session.mu.Unlock()
   301  
   302  	// if the rollback already happened on the savepoint. There is nothing to set or execute on later.
   303  	if session.savepointState == savepointRollback {
   304  		return
   305  	}
   306  
   307  	if session.savepointState == savepointSet {
   308  		session.rollbackOnPartialExec = fmt.Sprintf("rollback to %s", session.savepointName)
   309  	} else {
   310  		session.rollbackOnPartialExec = txRollback
   311  	}
   312  	session.savepointState = savepointRollbackSet
   313  }
   314  
   315  // SavepointRollback updates the state that transaction was rolledback to the savepoint stored in the session.
   316  func (session *SafeSession) SavepointRollback() {
   317  	session.mu.Lock()
   318  	defer session.mu.Unlock()
   319  
   320  	session.savepointState = savepointRollback
   321  }
   322  
   323  // IsRollbackSet returns true if rollback to savepoint can be done.
   324  func (session *SafeSession) IsRollbackSet() bool {
   325  	session.mu.Lock()
   326  	defer session.mu.Unlock()
   327  
   328  	return session.savepointState == savepointRollbackSet
   329  }
   330  
   331  // SetCommitOrder sets the commit order.
   332  func (session *SafeSession) SetCommitOrder(co vtgatepb.CommitOrder) {
   333  	session.mu.Lock()
   334  	defer session.mu.Unlock()
   335  	session.commitOrder = co
   336  }
   337  
   338  // InTransaction returns true if we are in a transaction
   339  func (session *SafeSession) InTransaction() bool {
   340  	session.mu.Lock()
   341  	defer session.mu.Unlock()
   342  	return session.Session.InTransaction
   343  }
   344  
   345  // FindAndChangeSessionIfInSingleTxMode returns the transactionId and tabletAlias, if any, for a session
   346  // modifies the shard session in a specific case for single mode transaction.
   347  func (session *SafeSession) FindAndChangeSessionIfInSingleTxMode(keyspace, shard string, tabletType topodatapb.TabletType, txMode vtgatepb.TransactionMode) (int64, int64, *topodatapb.TabletAlias, error) {
   348  	session.mu.Lock()
   349  	defer session.mu.Unlock()
   350  	sessions := session.ShardSessions
   351  	switch session.commitOrder {
   352  	case vtgatepb.CommitOrder_PRE:
   353  		sessions = session.PreSessions
   354  	case vtgatepb.CommitOrder_POST:
   355  		sessions = session.PostSessions
   356  	}
   357  	for _, shardSession := range sessions {
   358  		if keyspace == shardSession.Target.Keyspace && tabletType == shardSession.Target.TabletType && shard == shardSession.Target.Shard {
   359  			if txMode != vtgatepb.TransactionMode_SINGLE || !shardSession.VindexOnly || session.queryFromVindex {
   360  				return shardSession.TransactionId, shardSession.ReservedId, shardSession.TabletAlias, nil
   361  			}
   362  			count := actualNoOfShardSession(session.ShardSessions)
   363  			// If the count of shard session which are non vindex only is greater than 0, then it is a
   364  			if count > 0 {
   365  				session.mustRollback = true
   366  				return 0, 0, nil, vterrors.Errorf(vtrpcpb.Code_ABORTED, "multi-db transaction attempted: %v", session.ShardSessions)
   367  			}
   368  			// the shard session is now used by non-vindex query as well,
   369  			// so it is not an exclusive vindex only shard session anymore.
   370  			shardSession.VindexOnly = false
   371  			return shardSession.TransactionId, shardSession.ReservedId, shardSession.TabletAlias, nil
   372  		}
   373  	}
   374  	return 0, 0, nil, nil
   375  }
   376  
   377  func addOrUpdate(shardSession *vtgatepb.Session_ShardSession, sessions []*vtgatepb.Session_ShardSession) ([]*vtgatepb.Session_ShardSession, error) {
   378  	appendSession := true
   379  	for i, sess := range sessions {
   380  		targetedAtSameTablet := sess.Target.Keyspace == shardSession.Target.Keyspace &&
   381  			sess.Target.TabletType == shardSession.Target.TabletType &&
   382  			sess.Target.Shard == shardSession.Target.Shard
   383  		if targetedAtSameTablet {
   384  			if !proto.Equal(sess.TabletAlias, shardSession.TabletAlias) {
   385  				errorDetails := fmt.Sprintf("got non-matching aliases (%v vs %v) for the same target (keyspace: %v, tabletType: %v, shard: %v)",
   386  					sess.TabletAlias, shardSession.TabletAlias,
   387  					sess.Target.Keyspace, sess.Target.TabletType, sess.Target.Shard)
   388  				return nil, vterrors.New(vtrpcpb.Code_FAILED_PRECONDITION, errorDetails)
   389  			}
   390  			// replace the old info with the new one
   391  			sessions[i] = shardSession
   392  			appendSession = false
   393  			break
   394  		}
   395  	}
   396  	if appendSession {
   397  		sessions = append(sessions, shardSession)
   398  	}
   399  
   400  	return sessions, nil
   401  }
   402  
   403  // AppendOrUpdate adds a new ShardSession, or updates an existing one if one already exists for the given shard session
   404  func (session *SafeSession) AppendOrUpdate(shardSession *vtgatepb.Session_ShardSession, txMode vtgatepb.TransactionMode) error {
   405  	session.mu.Lock()
   406  	defer session.mu.Unlock()
   407  
   408  	// additional check of transaction id is required
   409  	// as now in autocommit mode there can be session due to reserved connection
   410  	// that needs to be stored as shard session.
   411  	if session.autocommitState == autocommitted && shardSession.TransactionId != 0 {
   412  		// Should be unreachable
   413  		return vterrors.VT13001("unexpected 'autocommitted' state in transaction")
   414  	}
   415  	if !(session.Session.InTransaction || session.Session.InReservedConn) {
   416  		// Should be unreachable
   417  		return vterrors.VT13001("current session is neither in transaction nor in reserved connection")
   418  	}
   419  	session.autocommitState = notAutocommittable
   420  
   421  	// Always append, in order for rollback to succeed.
   422  	switch session.commitOrder {
   423  	case vtgatepb.CommitOrder_NORMAL:
   424  		if session.queryFromVindex {
   425  			shardSession.VindexOnly = true
   426  		}
   427  		newSessions, err := addOrUpdate(shardSession, session.ShardSessions)
   428  		if err != nil {
   429  			return err
   430  		}
   431  		session.ShardSessions = newSessions
   432  
   433  		if session.queryFromVindex {
   434  			break
   435  		}
   436  		// isSingle is enforced only for normmal commit order operations.
   437  		if session.isSingleDB(txMode) && len(session.ShardSessions) > 1 {
   438  			count := actualNoOfShardSession(session.ShardSessions)
   439  			if count <= 1 {
   440  				break
   441  			}
   442  			session.mustRollback = true
   443  			return vterrors.Errorf(vtrpcpb.Code_ABORTED, "multi-db transaction attempted: %v", session.ShardSessions)
   444  		}
   445  	case vtgatepb.CommitOrder_PRE:
   446  		newSessions, err := addOrUpdate(shardSession, session.PreSessions)
   447  		if err != nil {
   448  			return err
   449  		}
   450  		session.PreSessions = newSessions
   451  	case vtgatepb.CommitOrder_POST:
   452  		newSessions, err := addOrUpdate(shardSession, session.PostSessions)
   453  		if err != nil {
   454  			return err
   455  		}
   456  		session.PostSessions = newSessions
   457  	default:
   458  		// Should be unreachable
   459  		return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] SafeSession.AppendOrUpdate: unexpected commitOrder")
   460  	}
   461  
   462  	return nil
   463  }
   464  
   465  func actualNoOfShardSession(sessions []*vtgatepb.Session_ShardSession) int {
   466  	actualSS := 0
   467  	for _, ss := range sessions {
   468  		if ss.VindexOnly {
   469  			continue
   470  		}
   471  		actualSS++
   472  	}
   473  	return actualSS
   474  }
   475  
   476  func (session *SafeSession) isSingleDB(txMode vtgatepb.TransactionMode) bool {
   477  	return session.TransactionMode == vtgatepb.TransactionMode_SINGLE ||
   478  		(session.TransactionMode == vtgatepb.TransactionMode_UNSPECIFIED && txMode == vtgatepb.TransactionMode_SINGLE)
   479  }
   480  
   481  // SetRollback sets the flag indicating that the transaction must be rolled back.
   482  // The call is a no-op if the session is not in a transaction.
   483  func (session *SafeSession) SetRollback() {
   484  	session.mu.Lock()
   485  	defer session.mu.Unlock()
   486  	if session.Session.InTransaction {
   487  		session.mustRollback = true
   488  	}
   489  }
   490  
   491  // MustRollback returns true if the transaction must be rolled back.
   492  func (session *SafeSession) MustRollback() bool {
   493  	session.mu.Lock()
   494  	defer session.mu.Unlock()
   495  	return session.mustRollback
   496  }
   497  
   498  // RecordWarning stores the given warning in the session
   499  func (session *SafeSession) RecordWarning(warning *querypb.QueryWarning) {
   500  	session.mu.Lock()
   501  	defer session.mu.Unlock()
   502  	session.Session.Warnings = append(session.Session.Warnings, warning)
   503  }
   504  
   505  // ClearWarnings removes all the warnings from the session
   506  func (session *SafeSession) ClearWarnings() {
   507  	session.mu.Lock()
   508  	defer session.mu.Unlock()
   509  	session.Session.Warnings = nil
   510  }
   511  
   512  // SetUserDefinedVariable sets the user defined variable in the session.
   513  func (session *SafeSession) SetUserDefinedVariable(key string, value *querypb.BindVariable) {
   514  	session.mu.Lock()
   515  	defer session.mu.Unlock()
   516  	if session.UserDefinedVariables == nil {
   517  		session.UserDefinedVariables = make(map[string]*querypb.BindVariable)
   518  	}
   519  	session.UserDefinedVariables[key] = value
   520  }
   521  
   522  // SetTargetString sets the target string in the session.
   523  func (session *SafeSession) SetTargetString(target string) {
   524  	session.mu.Lock()
   525  	defer session.mu.Unlock()
   526  	session.TargetString = target
   527  }
   528  
   529  // SetSystemVariable sets the system variable in the session.
   530  func (session *SafeSession) SetSystemVariable(name string, expr string) {
   531  	session.mu.Lock()
   532  	defer session.mu.Unlock()
   533  	if session.SystemVariables == nil {
   534  		session.SystemVariables = make(map[string]string)
   535  	}
   536  	session.SystemVariables[name] = expr
   537  }
   538  
   539  // GetSystemVariables takes a visitor function that will receive each MySQL system variable in the session.
   540  // This function will only yield system variables which apply to MySQL itself; Vitess-aware system variables
   541  // will be skipped.
   542  func (session *SafeSession) GetSystemVariables(f func(k string, v string)) {
   543  	session.mu.Lock()
   544  	defer session.mu.Unlock()
   545  	for k, v := range session.SystemVariables {
   546  		if sysvars.IsVitessAware(k) {
   547  			continue
   548  		}
   549  		f(k, v)
   550  	}
   551  }
   552  
   553  // HasSystemVariables returns whether the session has system variables that would apply to MySQL
   554  func (session *SafeSession) HasSystemVariables() (found bool) {
   555  	session.GetSystemVariables(func(_ string, _ string) {
   556  		found = true
   557  	})
   558  	return
   559  }
   560  
   561  // SetOptions sets the options
   562  func (session *SafeSession) SetOptions(options *querypb.ExecuteOptions) {
   563  	session.mu.Lock()
   564  	defer session.mu.Unlock()
   565  	session.Options = options
   566  }
   567  
   568  // StoreSavepoint stores the savepoint and release savepoint queries in the session
   569  func (session *SafeSession) StoreSavepoint(sql string) {
   570  	session.mu.Lock()
   571  	defer session.mu.Unlock()
   572  	session.Savepoints = append(session.Savepoints, sql)
   573  }
   574  
   575  // InReservedConn returns true if the session needs to execute on a dedicated connection
   576  func (session *SafeSession) InReservedConn() bool {
   577  	session.mu.Lock()
   578  	defer session.mu.Unlock()
   579  	return session.Session.InReservedConn
   580  }
   581  
   582  // SetReservedConn set the InReservedConn setting.
   583  func (session *SafeSession) SetReservedConn(reservedConn bool) {
   584  	session.mu.Lock()
   585  	defer session.mu.Unlock()
   586  	session.Session.InReservedConn = reservedConn
   587  }
   588  
   589  // SetPreQueries returns the prequeries that need to be run when reserving a connection
   590  func (session *SafeSession) SetPreQueries() []string {
   591  	// extract keys
   592  	var keys []string
   593  	sysVars := make(map[string]string)
   594  	session.GetSystemVariables(func(k string, v string) {
   595  		keys = append(keys, k)
   596  		sysVars[k] = v
   597  	})
   598  
   599  	// if not system variables to set, return
   600  	if len(keys) == 0 {
   601  		return nil
   602  	}
   603  
   604  	// sort the keys
   605  	sort.Strings(keys)
   606  
   607  	// build the query using sorted keys
   608  	var preQuery strings.Builder
   609  	first := true
   610  	for _, k := range keys {
   611  		if first {
   612  			preQuery.WriteString(fmt.Sprintf("set %s = %s", k, sysVars[k]))
   613  			first = false
   614  		} else {
   615  			preQuery.WriteString(fmt.Sprintf(", %s = %s", k, sysVars[k]))
   616  		}
   617  	}
   618  	return []string{preQuery.String()}
   619  }
   620  
   621  // SetLockSession sets the lock session.
   622  func (session *SafeSession) SetLockSession(lockSession *vtgatepb.Session_ShardSession) {
   623  	session.mu.Lock()
   624  	defer session.mu.Unlock()
   625  	session.LockSession = lockSession
   626  	session.LastLockHeartbeat = time.Now().Unix()
   627  }
   628  
   629  // UpdateLockHeartbeat updates the LastLockHeartbeat time
   630  func (session *SafeSession) UpdateLockHeartbeat() {
   631  	session.mu.Lock()
   632  	defer session.mu.Unlock()
   633  	session.LastLockHeartbeat = time.Now().Unix()
   634  }
   635  
   636  // TriggerLockHeartBeat returns if it time to trigger next lock heartbeat
   637  func (session *SafeSession) TriggerLockHeartBeat() bool {
   638  	session.mu.Lock()
   639  	defer session.mu.Unlock()
   640  	now := time.Now().Unix()
   641  	return now-session.LastLockHeartbeat >= int64(lockHeartbeatTime.Seconds())
   642  }
   643  
   644  // InLockSession returns whether locking is used on this session.
   645  func (session *SafeSession) InLockSession() bool {
   646  	session.mu.Lock()
   647  	defer session.mu.Unlock()
   648  	return session.LockSession != nil
   649  }
   650  
   651  // ResetLock resets the lock session
   652  func (session *SafeSession) ResetLock() {
   653  	session.mu.Lock()
   654  	defer session.mu.Unlock()
   655  	session.LockSession = nil
   656  	session.AdvisoryLock = nil
   657  }
   658  
   659  // ResetShard reset the shard session for the provided tablet alias.
   660  func (session *SafeSession) ResetShard(tabletAlias *topodatapb.TabletAlias) error {
   661  	session.mu.Lock()
   662  	defer session.mu.Unlock()
   663  
   664  	// Always append, in order for rollback to succeed.
   665  	switch session.commitOrder {
   666  	case vtgatepb.CommitOrder_NORMAL:
   667  		newSessions, err := removeShard(tabletAlias, session.ShardSessions)
   668  		if err != nil {
   669  			return err
   670  		}
   671  		session.ShardSessions = newSessions
   672  	case vtgatepb.CommitOrder_PRE:
   673  		newSessions, err := removeShard(tabletAlias, session.PreSessions)
   674  		if err != nil {
   675  			return err
   676  		}
   677  		session.PreSessions = newSessions
   678  	case vtgatepb.CommitOrder_POST:
   679  		newSessions, err := removeShard(tabletAlias, session.PostSessions)
   680  		if err != nil {
   681  			return err
   682  		}
   683  		session.PostSessions = newSessions
   684  	default:
   685  		// Should be unreachable
   686  		return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] SafeSession.ResetShard: unexpected commitOrder")
   687  	}
   688  	return nil
   689  }
   690  
   691  // SetDDLStrategy set the DDLStrategy setting.
   692  func (session *SafeSession) SetDDLStrategy(strategy string) {
   693  	session.mu.Lock()
   694  	defer session.mu.Unlock()
   695  	session.DDLStrategy = strategy
   696  }
   697  
   698  // GetDDLStrategy returns the DDLStrategy value.
   699  func (session *SafeSession) GetDDLStrategy() string {
   700  	session.mu.Lock()
   701  	defer session.mu.Unlock()
   702  	return session.DDLStrategy
   703  }
   704  
   705  // GetSessionUUID returns the SessionUUID value.
   706  func (session *SafeSession) GetSessionUUID() string {
   707  	session.mu.Lock()
   708  	defer session.mu.Unlock()
   709  	return session.SessionUUID
   710  }
   711  
   712  // SetSessionEnableSystemSettings set the SessionEnableSystemSettings setting.
   713  func (session *SafeSession) SetSessionEnableSystemSettings(allow bool) {
   714  	session.mu.Lock()
   715  	defer session.mu.Unlock()
   716  	session.EnableSystemSettings = allow
   717  }
   718  
   719  // GetSessionEnableSystemSettings returns the SessionEnableSystemSettings value.
   720  func (session *SafeSession) GetSessionEnableSystemSettings() bool {
   721  	session.mu.Lock()
   722  	defer session.mu.Unlock()
   723  	return session.EnableSystemSettings
   724  }
   725  
   726  // SetReadAfterWriteGTID set the ReadAfterWriteGtid setting.
   727  func (session *SafeSession) SetReadAfterWriteGTID(vtgtid string) {
   728  	session.mu.Lock()
   729  	defer session.mu.Unlock()
   730  	if session.ReadAfterWrite == nil {
   731  		session.ReadAfterWrite = &vtgatepb.ReadAfterWrite{}
   732  	}
   733  	session.ReadAfterWrite.ReadAfterWriteGtid = vtgtid
   734  }
   735  
   736  // SetReadAfterWriteTimeout set the ReadAfterWriteTimeout setting.
   737  func (session *SafeSession) SetReadAfterWriteTimeout(timeout float64) {
   738  	session.mu.Lock()
   739  	defer session.mu.Unlock()
   740  	if session.ReadAfterWrite == nil {
   741  		session.ReadAfterWrite = &vtgatepb.ReadAfterWrite{}
   742  	}
   743  	session.ReadAfterWrite.ReadAfterWriteTimeout = timeout
   744  }
   745  
   746  // SetSessionTrackGtids set the SessionTrackGtids setting.
   747  func (session *SafeSession) SetSessionTrackGtids(enable bool) {
   748  	session.mu.Lock()
   749  	defer session.mu.Unlock()
   750  	if session.ReadAfterWrite == nil {
   751  		session.ReadAfterWrite = &vtgatepb.ReadAfterWrite{}
   752  	}
   753  	session.ReadAfterWrite.SessionTrackGtids = enable
   754  }
   755  
   756  func removeShard(tabletAlias *topodatapb.TabletAlias, sessions []*vtgatepb.Session_ShardSession) ([]*vtgatepb.Session_ShardSession, error) {
   757  	idx := -1
   758  	for i, session := range sessions {
   759  		if proto.Equal(session.TabletAlias, tabletAlias) {
   760  			if session.TransactionId != 0 {
   761  				return nil, vterrors.VT13001("removing shard session when in transaction")
   762  			}
   763  			idx = i
   764  		}
   765  	}
   766  	if idx == -1 {
   767  		return nil, vterrors.VT13001("tried to remove missing shard")
   768  	}
   769  	return append(sessions[:idx], sessions[idx+1:]...), nil
   770  }
   771  
   772  // GetOrCreateOptions will return the current options struct, or create one and return it if no-one exists
   773  func (session *SafeSession) GetOrCreateOptions() *querypb.ExecuteOptions {
   774  	if session.Session.Options == nil {
   775  		session.Session.Options = &querypb.ExecuteOptions{}
   776  	}
   777  	return session.Session.Options
   778  }
   779  
   780  var _ iQueryOption = (*SafeSession)(nil)
   781  
   782  func (session *SafeSession) cachePlan() bool {
   783  	if session == nil || session.Options == nil {
   784  		return true
   785  	}
   786  
   787  	session.mu.Lock()
   788  	defer session.mu.Unlock()
   789  
   790  	return !(session.Options.SkipQueryPlanCache || session.Options.HasCreatedTempTables)
   791  }
   792  
   793  func (session *SafeSession) getSelectLimit() int {
   794  	if session == nil || session.Options == nil {
   795  		return -1
   796  	}
   797  
   798  	session.mu.Lock()
   799  	defer session.mu.Unlock()
   800  
   801  	return int(session.Options.SqlSelectLimit)
   802  }
   803  
   804  // isTxOpen returns true if there is open connection to any of the shard.
   805  func (session *SafeSession) isTxOpen() bool {
   806  	session.mu.Lock()
   807  	defer session.mu.Unlock()
   808  
   809  	return len(session.ShardSessions) > 0 || len(session.PreSessions) > 0 || len(session.PostSessions) > 0
   810  }
   811  
   812  // getSessions returns the shard session for the current commit order.
   813  func (session *SafeSession) getSessions() []*vtgatepb.Session_ShardSession {
   814  	session.mu.Lock()
   815  	defer session.mu.Unlock()
   816  
   817  	switch session.commitOrder {
   818  	case vtgatepb.CommitOrder_PRE:
   819  		return session.PreSessions
   820  	case vtgatepb.CommitOrder_POST:
   821  		return session.PostSessions
   822  	default:
   823  		return session.ShardSessions
   824  	}
   825  }
   826  
   827  func (session *SafeSession) RemoveInternalSavepoint() {
   828  	session.mu.Lock()
   829  	defer session.mu.Unlock()
   830  
   831  	if session.savepointName == "" {
   832  		return
   833  	}
   834  	sCount := len(session.Savepoints)
   835  	if sCount == 0 {
   836  		return
   837  	}
   838  	sLast := sCount - 1
   839  	if strings.Contains(session.Savepoints[sLast], session.savepointName) {
   840  		session.Savepoints = session.Savepoints[0:sLast]
   841  	}
   842  }
   843  
   844  // HasAdvisoryLock returns if any advisory lock is taken
   845  func (session *SafeSession) HasAdvisoryLock() bool {
   846  	session.mu.Lock()
   847  	defer session.mu.Unlock()
   848  
   849  	return len(session.AdvisoryLock) != 0
   850  }
   851  
   852  // AddAdvisoryLock adds the advisory lock to the list.
   853  func (session *SafeSession) AddAdvisoryLock(name string) {
   854  	session.mu.Lock()
   855  	defer session.mu.Unlock()
   856  
   857  	if session.AdvisoryLock == nil {
   858  		session.AdvisoryLock = map[string]int64{name: 1}
   859  		return
   860  	}
   861  	count, exists := session.AdvisoryLock[name]
   862  	if exists {
   863  		count++
   864  	}
   865  	session.AdvisoryLock[name] = count
   866  }
   867  
   868  // RemoveAdvisoryLock removes the advisory lock from the list.
   869  func (session *SafeSession) RemoveAdvisoryLock(name string) {
   870  	session.mu.Lock()
   871  	defer session.mu.Unlock()
   872  
   873  	if session.AdvisoryLock == nil {
   874  		return
   875  	}
   876  	count, exists := session.AdvisoryLock[name]
   877  	if !exists {
   878  		return
   879  	}
   880  	count--
   881  	if count == 0 {
   882  		delete(session.AdvisoryLock, name)
   883  		return
   884  	}
   885  	session.AdvisoryLock[name] = count
   886  }
   887  
   888  // ClearAdvisoryLock clears the advisory lock list.
   889  func (session *SafeSession) ClearAdvisoryLock() {
   890  	session.mu.Lock()
   891  	defer session.mu.Unlock()
   892  
   893  	session.AdvisoryLock = nil
   894  }
   895  
   896  func (session *SafeSession) EnableLogging() {
   897  	session.mu.Lock()
   898  	defer session.mu.Unlock()
   899  
   900  	session.logging = &executeLogger{}
   901  }
   902  
   903  func (l *executeLogger) log(primitive engine.Primitive, target *querypb.Target, gateway srvtopo.Gateway, query string, begin bool, bv map[string]*querypb.BindVariable) {
   904  	if l == nil {
   905  		return
   906  	}
   907  	l.mu.Lock()
   908  	defer l.mu.Unlock()
   909  	id := l.lastID
   910  	l.lastID++
   911  	if begin {
   912  		l.entries = append(l.entries, engine.ExecuteEntry{
   913  			ID:        id,
   914  			Target:    target,
   915  			Gateway:   gateway,
   916  			Query:     "begin",
   917  			FiredFrom: primitive,
   918  		})
   919  	}
   920  	ast, err := sqlparser.Parse(query)
   921  	if err != nil {
   922  		panic("query not able to parse. this should not happen")
   923  	}
   924  	pq := sqlparser.NewParsedQuery(ast)
   925  	if bv == nil {
   926  		bv = map[string]*querypb.BindVariable{}
   927  	}
   928  	q, err := pq.GenerateQuery(bv, nil)
   929  	if err != nil {
   930  		panic("query not able to generate query. this should not happen")
   931  	}
   932  
   933  	l.entries = append(l.entries, engine.ExecuteEntry{
   934  		ID:        id,
   935  		Target:    target,
   936  		Gateway:   gateway,
   937  		Query:     q,
   938  		FiredFrom: primitive,
   939  	})
   940  }
   941  
   942  func (l *executeLogger) GetLogs() []engine.ExecuteEntry {
   943  	l.mu.Lock()
   944  	defer l.mu.Unlock()
   945  	result := make([]engine.ExecuteEntry, len(l.entries))
   946  	copy(result, l.entries)
   947  	return result
   948  }