vitess.io/vitess@v0.16.2/go/vt/binlog/binlogplayer/binlog_player.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 binlogplayer contains the code that plays a vreplication
    18  // stream on a client database. It usually runs inside the destination primary
    19  // vttablet process.
    20  package binlogplayer
    21  
    22  import (
    23  	"bytes"
    24  	"compress/zlib"
    25  	"context"
    26  	"encoding/binary"
    27  	"encoding/hex"
    28  	"fmt"
    29  	"io"
    30  	"math"
    31  	"os"
    32  	"sync"
    33  	"time"
    34  
    35  	"github.com/spf13/pflag"
    36  
    37  	"google.golang.org/protobuf/proto"
    38  
    39  	"vitess.io/vitess/go/history"
    40  	"vitess.io/vitess/go/mysql"
    41  	"vitess.io/vitess/go/sqltypes"
    42  	"vitess.io/vitess/go/stats"
    43  	"vitess.io/vitess/go/sync2"
    44  	"vitess.io/vitess/go/vt/log"
    45  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    46  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    47  	"vitess.io/vitess/go/vt/servenv"
    48  	"vitess.io/vitess/go/vt/throttler"
    49  )
    50  
    51  var (
    52  	// SlowQueryThreshold will cause we logging anything that's higher than it.
    53  	SlowQueryThreshold = time.Duration(100 * time.Millisecond)
    54  
    55  	// keys for the stats map
    56  
    57  	// BlplQuery is the key for the stats map.
    58  	BlplQuery = "Query"
    59  	// BlplTransaction is the key for the stats map.
    60  	BlplTransaction = "Transaction"
    61  
    62  	// VReplicationInit is for the Init state.
    63  	VReplicationInit = "Init"
    64  	// VReplicationCopying is for the Copying state.
    65  	VReplicationCopying = "Copying"
    66  	// BlpRunning is for the Running state.
    67  	BlpRunning = "Running"
    68  	// BlpStopped is for the Stopped state.
    69  	BlpStopped = "Stopped"
    70  	// BlpError is for the Error state.
    71  	BlpError = "Error"
    72  )
    73  
    74  // Stats is the internal stats of a player. It is a different
    75  // structure that is passed in so stats can be collected over the life
    76  // of multiple individual players.
    77  type Stats struct {
    78  	// Stats about the player, keys used are BlplQuery and BlplTransaction
    79  	Timings *stats.Timings
    80  	Rates   *stats.Rates
    81  
    82  	// Last saved status
    83  	lastPositionMutex sync.Mutex
    84  	lastPosition      mysql.Position
    85  
    86  	heartbeatMutex sync.Mutex
    87  	heartbeat      int64
    88  
    89  	ReplicationLagSeconds sync2.AtomicInt64
    90  	History               *history.History
    91  
    92  	State sync2.AtomicString
    93  
    94  	PhaseTimings   *stats.Timings
    95  	QueryTimings   *stats.Timings
    96  	QueryCount     *stats.CountersWithSingleLabel
    97  	CopyRowCount   *stats.Counter
    98  	CopyLoopCount  *stats.Counter
    99  	ErrorCounts    *stats.CountersWithMultiLabels
   100  	NoopQueryCount *stats.CountersWithSingleLabel
   101  
   102  	VReplicationLags     *stats.Timings
   103  	VReplicationLagRates *stats.Rates
   104  
   105  	TableCopyRowCounts *stats.CountersWithSingleLabel
   106  	TableCopyTimings   *stats.Timings
   107  }
   108  
   109  // RecordHeartbeat updates the time the last heartbeat from vstreamer was seen
   110  func (bps *Stats) RecordHeartbeat(tm int64) {
   111  	bps.heartbeatMutex.Lock()
   112  	defer bps.heartbeatMutex.Unlock()
   113  	bps.heartbeat = tm
   114  }
   115  
   116  // Heartbeat gets the time the last heartbeat from vstreamer was seen
   117  func (bps *Stats) Heartbeat() int64 {
   118  	bps.heartbeatMutex.Lock()
   119  	defer bps.heartbeatMutex.Unlock()
   120  	return bps.heartbeat
   121  }
   122  
   123  // SetLastPosition sets the last replication position.
   124  func (bps *Stats) SetLastPosition(pos mysql.Position) {
   125  	bps.lastPositionMutex.Lock()
   126  	defer bps.lastPositionMutex.Unlock()
   127  	bps.lastPosition = pos
   128  }
   129  
   130  // LastPosition gets the last replication position.
   131  func (bps *Stats) LastPosition() mysql.Position {
   132  	bps.lastPositionMutex.Lock()
   133  	defer bps.lastPositionMutex.Unlock()
   134  	return bps.lastPosition
   135  }
   136  
   137  // MessageHistory gets all the messages, we store 3 at a time
   138  func (bps *Stats) MessageHistory() []string {
   139  	strs := make([]string, 0, 3)
   140  	for _, h := range bps.History.Records() {
   141  		h1, _ := h.(*StatsHistoryRecord)
   142  		if h1 != nil {
   143  			strs = append(strs, h1.Message)
   144  		}
   145  	}
   146  	return strs
   147  }
   148  
   149  // NewStats creates a new Stats structure.
   150  func NewStats() *Stats {
   151  	bps := &Stats{}
   152  	bps.Timings = stats.NewTimings("", "", "")
   153  	bps.Rates = stats.NewRates("", bps.Timings, 15*60/5, 5*time.Second)
   154  	bps.History = history.New(3)
   155  	bps.ReplicationLagSeconds.Set(math.MaxInt64)
   156  	bps.PhaseTimings = stats.NewTimings("", "", "Phase")
   157  	bps.QueryTimings = stats.NewTimings("", "", "Phase")
   158  	bps.QueryCount = stats.NewCountersWithSingleLabel("", "", "Phase", "")
   159  	bps.CopyRowCount = stats.NewCounter("", "")
   160  	bps.CopyLoopCount = stats.NewCounter("", "")
   161  	bps.ErrorCounts = stats.NewCountersWithMultiLabels("", "", []string{"type"})
   162  	bps.NoopQueryCount = stats.NewCountersWithSingleLabel("", "", "Statement", "")
   163  	bps.VReplicationLags = stats.NewTimings("", "", "")
   164  	bps.VReplicationLagRates = stats.NewRates("", bps.VReplicationLags, 15*60/5, 5*time.Second)
   165  	bps.TableCopyRowCounts = stats.NewCountersWithSingleLabel("", "", "Table", "")
   166  	bps.TableCopyTimings = stats.NewTimings("", "", "Table")
   167  	return bps
   168  }
   169  
   170  // BinlogPlayer is for reading a stream of updates from BinlogServer.
   171  type BinlogPlayer struct {
   172  	tablet   *topodatapb.Tablet
   173  	dbClient DBClient
   174  
   175  	// for key range base requests
   176  	keyRange *topodatapb.KeyRange
   177  
   178  	// for table base requests
   179  	tables []string
   180  
   181  	// common to all
   182  	uid            uint32
   183  	position       mysql.Position
   184  	stopPosition   mysql.Position
   185  	blplStats      *Stats
   186  	defaultCharset *binlogdatapb.Charset
   187  	currentCharset *binlogdatapb.Charset
   188  	deadlockRetry  time.Duration
   189  }
   190  
   191  // NewBinlogPlayerKeyRange returns a new BinlogPlayer pointing at the server
   192  // replicating the provided keyrange and updating _vt.vreplication
   193  // with uid=startPosition.Uid.
   194  // If !stopPosition.IsZero(), it will stop when reaching that position.
   195  func NewBinlogPlayerKeyRange(dbClient DBClient, tablet *topodatapb.Tablet, keyRange *topodatapb.KeyRange, uid uint32, blplStats *Stats) *BinlogPlayer {
   196  	result := &BinlogPlayer{
   197  		tablet:        tablet,
   198  		dbClient:      dbClient,
   199  		keyRange:      keyRange,
   200  		uid:           uid,
   201  		blplStats:     blplStats,
   202  		deadlockRetry: 1 * time.Second,
   203  	}
   204  	return result
   205  }
   206  
   207  // NewBinlogPlayerTables returns a new BinlogPlayer pointing at the server
   208  // replicating the provided tables and updating _vt.vreplication
   209  // with uid=startPosition.Uid.
   210  // If !stopPosition.IsZero(), it will stop when reaching that position.
   211  func NewBinlogPlayerTables(dbClient DBClient, tablet *topodatapb.Tablet, tables []string, uid uint32, blplStats *Stats) *BinlogPlayer {
   212  	result := &BinlogPlayer{
   213  		tablet:        tablet,
   214  		dbClient:      dbClient,
   215  		tables:        tables,
   216  		uid:           uid,
   217  		blplStats:     blplStats,
   218  		deadlockRetry: 1 * time.Second,
   219  	}
   220  	return result
   221  }
   222  
   223  // ApplyBinlogEvents makes an RPC request to BinlogServer
   224  // and processes the events. It returns nil if the provided context
   225  // was canceled, or if we reached the stopping point.
   226  // If an error is encountered, it updates the vreplication state to "Error".
   227  // If a stop position was specified, and reached, the state is updated to "Stopped".
   228  func (blp *BinlogPlayer) ApplyBinlogEvents(ctx context.Context) error {
   229  	if err := blp.setVReplicationState(BlpRunning, ""); err != nil {
   230  		log.Errorf("Error writing Running state: %v", err)
   231  	}
   232  
   233  	if err := blp.applyEvents(ctx); err != nil {
   234  		if err := blp.setVReplicationState(BlpError, err.Error()); err != nil {
   235  			log.Errorf("Error writing stop state: %v", err)
   236  		}
   237  		return err
   238  	}
   239  	return nil
   240  }
   241  
   242  // applyEvents returns a recordable status message on termination or an error otherwise.
   243  func (blp *BinlogPlayer) applyEvents(ctx context.Context) error {
   244  	// Read starting values for vreplication.
   245  	settings, err := ReadVRSettings(blp.dbClient, blp.uid)
   246  	if err != nil {
   247  		log.Error(err)
   248  		return err
   249  	}
   250  
   251  	blp.position = settings.StartPos
   252  	blp.stopPosition = settings.StopPos
   253  	t, err := throttler.NewThrottler(
   254  		fmt.Sprintf("BinlogPlayer/%d", blp.uid),
   255  		"transactions",
   256  		1, /* threadCount */
   257  		settings.MaxTPS,
   258  		settings.MaxReplicationLag,
   259  	)
   260  	if err != nil {
   261  		err := fmt.Errorf("failed to instantiate throttler: %v", err)
   262  		log.Error(err)
   263  		return err
   264  	}
   265  	defer t.Close()
   266  
   267  	// Log the mode of operation and when the player stops.
   268  	if len(blp.tables) > 0 {
   269  		log.Infof("BinlogPlayer client %v for tables %v starting @ '%v', server: %v",
   270  			blp.uid,
   271  			blp.tables,
   272  			blp.position,
   273  			blp.tablet,
   274  		)
   275  	} else {
   276  		log.Infof("BinlogPlayer client %v for keyrange '%v-%v' starting @ '%v', server: %v",
   277  			blp.uid,
   278  			hex.EncodeToString(blp.keyRange.GetStart()),
   279  			hex.EncodeToString(blp.keyRange.GetEnd()),
   280  			blp.position,
   281  			blp.tablet,
   282  		)
   283  	}
   284  	if !blp.stopPosition.IsZero() {
   285  		switch {
   286  		case blp.position.Equal(blp.stopPosition):
   287  			msg := fmt.Sprintf("not starting BinlogPlayer, we're already at the desired position %v", blp.stopPosition)
   288  			log.Info(msg)
   289  			if err := blp.setVReplicationState(BlpStopped, msg); err != nil {
   290  				log.Errorf("Error writing stop state: %v", err)
   291  			}
   292  			return nil
   293  		case blp.position.AtLeast(blp.stopPosition):
   294  			msg := fmt.Sprintf("starting point %v greater than stopping point %v", blp.position, blp.stopPosition)
   295  			log.Error(msg)
   296  			if err := blp.setVReplicationState(BlpStopped, msg); err != nil {
   297  				log.Errorf("Error writing stop state: %v", err)
   298  			}
   299  			// Don't return an error. Otherwise, it will keep retrying.
   300  			return nil
   301  		default:
   302  			log.Infof("Will stop player when reaching %v", blp.stopPosition)
   303  		}
   304  	}
   305  
   306  	clientFactory, ok := clientFactories[binlogPlayerProtocol]
   307  	if !ok {
   308  		return fmt.Errorf("no binlog player client factory named %v", binlogPlayerProtocol)
   309  	}
   310  	blplClient := clientFactory()
   311  	err = blplClient.Dial(blp.tablet)
   312  	if err != nil {
   313  		err := fmt.Errorf("error dialing binlog server: %v", err)
   314  		log.Error(err)
   315  		return err
   316  	}
   317  	defer blplClient.Close()
   318  
   319  	// Get the current charset of our connection, so we can ask the stream server
   320  	// to check that they match. The streamer will also only send per-statement
   321  	// charset data if that statement's charset is different from what we specify.
   322  	if dbClient, ok := blp.dbClient.(*dbClientImpl); ok {
   323  		blp.defaultCharset, err = mysql.GetCharset(dbClient.dbConn)
   324  		if err != nil {
   325  			return fmt.Errorf("can't get charset to request binlog stream: %v", err)
   326  		}
   327  		log.Infof("original charset: %v", blp.defaultCharset)
   328  		blp.currentCharset = blp.defaultCharset
   329  		// Restore original charset when we're done.
   330  		defer func() {
   331  			// If the connection has been closed, there's no need to restore
   332  			// this connection-specific setting.
   333  			if dbClient.dbConn == nil {
   334  				return
   335  			}
   336  			log.Infof("restoring original charset %v", blp.defaultCharset)
   337  			if csErr := mysql.SetCharset(dbClient.dbConn, blp.defaultCharset); csErr != nil {
   338  				log.Errorf("can't restore original charset %v: %v", blp.defaultCharset, csErr)
   339  			}
   340  		}()
   341  	}
   342  
   343  	var stream BinlogTransactionStream
   344  	if len(blp.tables) > 0 {
   345  		stream, err = blplClient.StreamTables(ctx, mysql.EncodePosition(blp.position), blp.tables, blp.defaultCharset)
   346  	} else {
   347  		stream, err = blplClient.StreamKeyRange(ctx, mysql.EncodePosition(blp.position), blp.keyRange, blp.defaultCharset)
   348  	}
   349  	if err != nil {
   350  		err := fmt.Errorf("error sending streaming query to binlog server: %v", err)
   351  		log.Error(err)
   352  		return err
   353  	}
   354  
   355  	for {
   356  		// Block if we are throttled.
   357  		for {
   358  			backoff := t.Throttle(0 /* threadID */)
   359  			if backoff == throttler.NotThrottled {
   360  				break
   361  			}
   362  			// We don't bother checking for context cancellation here because the
   363  			// sleep will block only up to 1 second. (Usually, backoff is 1s / rate
   364  			// e.g. a rate of 1000 TPS results into a backoff of 1 ms.)
   365  			time.Sleep(backoff)
   366  		}
   367  
   368  		// get the response
   369  		response, err := stream.Recv()
   370  		// Check context before checking error, because canceled
   371  		// contexts could be wrapped as regular errors.
   372  		select {
   373  		case <-ctx.Done():
   374  			return nil
   375  		default:
   376  		}
   377  		if err != nil {
   378  			return fmt.Errorf("error received from Stream %v", err)
   379  		}
   380  
   381  		// process the transaction
   382  		for {
   383  			ok, err = blp.processTransaction(response)
   384  			if err != nil {
   385  				log.Infof("transaction failed: %v", err)
   386  				for _, stmt := range response.Statements {
   387  					log.Infof("statement: %q", stmt.Sql)
   388  				}
   389  				return fmt.Errorf("error in processing binlog event %v", err)
   390  			}
   391  			if ok {
   392  				if !blp.stopPosition.IsZero() {
   393  					if blp.position.AtLeast(blp.stopPosition) {
   394  						msg := "Reached stopping position, done playing logs"
   395  						log.Info(msg)
   396  						if err := blp.setVReplicationState(BlpStopped, msg); err != nil {
   397  							log.Errorf("Error writing stop state: %v", err)
   398  						}
   399  						return nil
   400  					}
   401  				}
   402  				break
   403  			}
   404  			log.Infof("Retrying txn in %v.", blp.deadlockRetry)
   405  			time.Sleep(blp.deadlockRetry)
   406  		}
   407  	}
   408  }
   409  
   410  func (blp *BinlogPlayer) processTransaction(tx *binlogdatapb.BinlogTransaction) (ok bool, err error) {
   411  	txnStartTime := time.Now()
   412  	if err = blp.dbClient.Begin(); err != nil {
   413  		return false, fmt.Errorf("failed query BEGIN, err: %s", err)
   414  	}
   415  	for i, stmt := range tx.Statements {
   416  		// Make sure the statement is replayed in the proper charset.
   417  		if dbClient, ok := blp.dbClient.(*dbClientImpl); ok {
   418  			var stmtCharset *binlogdatapb.Charset
   419  			if stmt.Charset != nil {
   420  				stmtCharset = stmt.Charset
   421  			} else {
   422  				// Streamer sends a nil Charset for statements that use the
   423  				// charset we specified in the request.
   424  				stmtCharset = blp.defaultCharset
   425  			}
   426  			if !proto.Equal(blp.currentCharset, stmtCharset) {
   427  				// In regular MySQL replication, the charset is silently adjusted as
   428  				// needed during event playback. Here we also adjust so that playback
   429  				// proceeds, but in Vitess-land this usually means a misconfigured
   430  				// server or a misbehaving client, so we spam the logs with warnings.
   431  				log.Warningf("BinlogPlayer changing charset from %v to %v for statement %d in transaction %v", blp.currentCharset, stmtCharset, i, tx)
   432  				err = mysql.SetCharset(dbClient.dbConn, stmtCharset)
   433  				if err != nil {
   434  					return false, fmt.Errorf("can't set charset for statement %d in transaction %v: %v", i, tx, err)
   435  				}
   436  				blp.currentCharset = stmtCharset
   437  			}
   438  		}
   439  		if _, err = blp.exec(string(stmt.Sql)); err == nil {
   440  			continue
   441  		}
   442  		if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Number() == mysql.ERLockDeadlock {
   443  			// Deadlock: ask for retry
   444  			log.Infof("Deadlock: %v", err)
   445  			if err = blp.dbClient.Rollback(); err != nil {
   446  				return false, err
   447  			}
   448  			return false, nil
   449  		}
   450  		_ = blp.dbClient.Rollback()
   451  		return false, err
   452  	}
   453  	// Update recovery position after successful replay.
   454  	// This also updates the blp's internal position.
   455  	if err = blp.writeRecoveryPosition(tx); err != nil {
   456  		_ = blp.dbClient.Rollback()
   457  		return false, err
   458  	}
   459  	if err = blp.dbClient.Commit(); err != nil {
   460  		return false, fmt.Errorf("failed query COMMIT, err: %s", err)
   461  	}
   462  	blp.blplStats.Timings.Record(BlplTransaction, txnStartTime)
   463  	return true, nil
   464  }
   465  
   466  func (blp *BinlogPlayer) exec(sql string) (*sqltypes.Result, error) {
   467  	queryStartTime := time.Now()
   468  	qr, err := blp.dbClient.ExecuteFetch(sql, 0)
   469  	blp.blplStats.Timings.Record(BlplQuery, queryStartTime)
   470  	if d := time.Since(queryStartTime); d > SlowQueryThreshold {
   471  		log.Infof("SLOW QUERY (took %.2fs) '%s'", d.Seconds(), sql)
   472  	}
   473  	return qr, err
   474  }
   475  
   476  // writeRecoveryPosition writes the current GTID as the recovery position
   477  // for the next transaction.
   478  // It also tries to get the timestamp for the transaction. Two cases:
   479  //   - we have statements, and they start with a SET TIMESTAMP that we
   480  //     can parse: then we update transaction_timestamp in vreplication
   481  //     with it, and set ReplicationLagSeconds to now() - transaction_timestamp
   482  //   - otherwise (the statements are probably filtered out), we leave
   483  //     transaction_timestamp alone (keeping the old value), and we don't
   484  //     change ReplicationLagSeconds
   485  func (blp *BinlogPlayer) writeRecoveryPosition(tx *binlogdatapb.BinlogTransaction) error {
   486  	position, err := DecodePosition(tx.EventToken.Position)
   487  	if err != nil {
   488  		return err
   489  	}
   490  
   491  	now := time.Now().Unix()
   492  	updateRecovery := GenerateUpdatePos(blp.uid, position, now, tx.EventToken.Timestamp, blp.blplStats.CopyRowCount.Get(), false)
   493  
   494  	qr, err := blp.exec(updateRecovery)
   495  	if err != nil {
   496  		return fmt.Errorf("error %v in writing recovery info %v", err, updateRecovery)
   497  	}
   498  	if qr.RowsAffected != 1 {
   499  		return fmt.Errorf("cannot update vreplication table, affected %v rows", qr.RowsAffected)
   500  	}
   501  
   502  	// Update position after successful write.
   503  	blp.position = position
   504  	blp.blplStats.SetLastPosition(blp.position)
   505  	if tx.EventToken.Timestamp != 0 {
   506  		blp.blplStats.ReplicationLagSeconds.Set(now - tx.EventToken.Timestamp)
   507  	}
   508  	return nil
   509  }
   510  
   511  func (blp *BinlogPlayer) setVReplicationState(state, message string) error {
   512  	if message != "" {
   513  		blp.blplStats.History.Add(&StatsHistoryRecord{
   514  			Time:    time.Now(),
   515  			Message: message,
   516  		})
   517  	}
   518  	blp.blplStats.State.Set(state)
   519  	query := fmt.Sprintf("update _vt.vreplication set state='%v', message=%v where id=%v", state, encodeString(MessageTruncate(message)), blp.uid)
   520  	if _, err := blp.dbClient.ExecuteFetch(query, 1); err != nil {
   521  		return fmt.Errorf("could not set state: %v: %v", query, err)
   522  	}
   523  	return nil
   524  }
   525  
   526  // VRSettings contains the settings of a vreplication table.
   527  type VRSettings struct {
   528  	StartPos           mysql.Position
   529  	StopPos            mysql.Position
   530  	MaxTPS             int64
   531  	MaxReplicationLag  int64
   532  	State              string
   533  	WorkflowType       int32
   534  	WorkflowSubType    int32
   535  	WorkflowName       string
   536  	DeferSecondaryKeys bool
   537  }
   538  
   539  // ReadVRSettings retrieves the throttler settings for
   540  // vreplication from the checkpoint table.
   541  func ReadVRSettings(dbClient DBClient, uid uint32) (VRSettings, error) {
   542  	query := fmt.Sprintf("select pos, stop_pos, max_tps, max_replication_lag, state, workflow_type, workflow, workflow_sub_type, defer_secondary_keys from _vt.vreplication where id=%v", uid)
   543  	qr, err := dbClient.ExecuteFetch(query, 1)
   544  	if err != nil {
   545  		return VRSettings{}, fmt.Errorf("error %v in selecting vreplication settings %v", err, query)
   546  	}
   547  
   548  	if len(qr.Rows) != 1 {
   549  		return VRSettings{}, fmt.Errorf("checkpoint information not available in db for %v", uid)
   550  	}
   551  	vrRow := qr.Named().Row()
   552  
   553  	maxTPS, err := vrRow.ToInt64("max_tps")
   554  	if err != nil {
   555  		return VRSettings{}, fmt.Errorf("failed to parse max_tps column2: %v", err)
   556  	}
   557  	maxReplicationLag, err := vrRow.ToInt64("max_replication_lag")
   558  	if err != nil {
   559  		return VRSettings{}, fmt.Errorf("failed to parse max_replication_lag column: %v", err)
   560  	}
   561  	startPos, err := DecodePosition(vrRow.AsString("pos", ""))
   562  	if err != nil {
   563  		return VRSettings{}, fmt.Errorf("failed to parse pos column: %v", err)
   564  	}
   565  	stopPos, err := mysql.DecodePosition(vrRow.AsString("stop_pos", ""))
   566  	if err != nil {
   567  		return VRSettings{}, fmt.Errorf("failed to parse stop_pos column: %v", err)
   568  	}
   569  	workflowTypeTmp, err := vrRow.ToInt64("workflow_type")
   570  	workflowType := int32(workflowTypeTmp)
   571  	if err != nil {
   572  		return VRSettings{}, fmt.Errorf("failed to parse workflow_type column: %v", err)
   573  	}
   574  	workflowSubTypeTmp, err := vrRow.ToInt64("workflow_sub_type")
   575  	workflowSubType := int32(workflowSubTypeTmp)
   576  	if err != nil {
   577  		return VRSettings{}, fmt.Errorf("failed to parse workflow_sub_type column: %v", err)
   578  	}
   579  	deferSecondaryKeys, err := vrRow.ToBool("defer_secondary_keys")
   580  	if err != nil {
   581  		return VRSettings{}, fmt.Errorf("failed to parse defer_secondary_keys column: %v", err)
   582  	}
   583  	return VRSettings{
   584  		StartPos:           startPos,
   585  		StopPos:            stopPos,
   586  		MaxTPS:             maxTPS,
   587  		MaxReplicationLag:  maxReplicationLag,
   588  		State:              vrRow.AsString("state", ""),
   589  		WorkflowType:       workflowType,
   590  		WorkflowName:       vrRow.AsString("workflow", ""),
   591  		WorkflowSubType:    workflowSubType,
   592  		DeferSecondaryKeys: deferSecondaryKeys,
   593  	}, nil
   594  }
   595  
   596  // CreateVReplication returns a statement to populate the first value into
   597  // the _vt.vreplication table.
   598  func CreateVReplication(workflow string, source *binlogdatapb.BinlogSource, position string, maxTPS, maxReplicationLag, timeUpdated int64, dbName string,
   599  	workflowType binlogdatapb.VReplicationWorkflowType, workflowSubType binlogdatapb.VReplicationWorkflowSubType, deferSecondaryKeys bool) string {
   600  	return fmt.Sprintf("insert into _vt.vreplication "+
   601  		"(workflow, source, pos, max_tps, max_replication_lag, time_updated, transaction_timestamp, state, db_name, workflow_type, workflow_sub_type, defer_secondary_keys) "+
   602  		"values (%v, %v, %v, %v, %v, %v, 0, '%v', %v, %v, %v, %v)",
   603  		encodeString(workflow), encodeString(source.String()), encodeString(position), maxTPS, maxReplicationLag,
   604  		timeUpdated, BlpRunning, encodeString(dbName), int64(workflowType), int64(workflowSubType), deferSecondaryKeys)
   605  }
   606  
   607  // CreateVReplicationState returns a statement to create a stopped vreplication.
   608  func CreateVReplicationState(workflow string, source *binlogdatapb.BinlogSource, position, state string, dbName string,
   609  	workflowType binlogdatapb.VReplicationWorkflowType, workflowSubType binlogdatapb.VReplicationWorkflowSubType) string {
   610  	return fmt.Sprintf("insert into _vt.vreplication "+
   611  		"(workflow, source, pos, max_tps, max_replication_lag, time_updated, transaction_timestamp, state, db_name, workflow_type, workflow_sub_type) "+
   612  		"values (%v, %v, %v, %v, %v, %v, 0, '%v', %v, %v, %v)",
   613  		encodeString(workflow), encodeString(source.String()), encodeString(position), throttler.MaxRateModuleDisabled,
   614  		throttler.ReplicationLagModuleDisabled, time.Now().Unix(), state, encodeString(dbName),
   615  		int64(workflowType), int64(workflowSubType))
   616  }
   617  
   618  // GenerateUpdatePos returns a statement to record the latest processed gtid in the _vt.vreplication table.
   619  func GenerateUpdatePos(uid uint32, pos mysql.Position, timeUpdated int64, txTimestamp int64, rowsCopied int64, compress bool) string {
   620  	strGTID := encodeString(mysql.EncodePosition(pos))
   621  	if compress {
   622  		strGTID = fmt.Sprintf("compress(%s)", strGTID)
   623  	}
   624  	if txTimestamp != 0 {
   625  		return fmt.Sprintf(
   626  			"update _vt.vreplication set pos=%v, time_updated=%v, transaction_timestamp=%v, rows_copied=%v, message='' where id=%v",
   627  			strGTID, timeUpdated, txTimestamp, rowsCopied, uid)
   628  	}
   629  	return fmt.Sprintf(
   630  		"update _vt.vreplication set pos=%v, time_updated=%v, rows_copied=%v, message='' where id=%v", strGTID, timeUpdated, rowsCopied, uid)
   631  }
   632  
   633  // GenerateUpdateRowsCopied returns a statement to update the rows_copied value in the _vt.vreplication table.
   634  func GenerateUpdateRowsCopied(uid uint32, rowsCopied int64) string {
   635  	return fmt.Sprintf("update _vt.vreplication set rows_copied=%v where id=%v", rowsCopied, uid)
   636  }
   637  
   638  // GenerateUpdateHeartbeat returns a statement to record the latest heartbeat in the _vt.vreplication table.
   639  func GenerateUpdateHeartbeat(uid uint32, timeUpdated int64) (string, error) {
   640  	if timeUpdated == 0 {
   641  		return "", fmt.Errorf("timeUpdated cannot be zero")
   642  	}
   643  	return fmt.Sprintf("update _vt.vreplication set time_updated=%v, time_heartbeat=%v where id=%v", timeUpdated, timeUpdated, uid), nil
   644  }
   645  
   646  // GenerateUpdateTimeThrottled returns a statement to record the latest throttle time in the _vt.vreplication table.
   647  func GenerateUpdateTimeThrottled(uid uint32, timeThrottledUnix int64, componentThrottled string) (string, error) {
   648  	if timeThrottledUnix == 0 {
   649  		return "", fmt.Errorf("timeUpdated cannot be zero")
   650  	}
   651  	return fmt.Sprintf("update _vt.vreplication set time_updated=%v, time_throttled=%v, component_throttled='%v' where id=%v", timeThrottledUnix, timeThrottledUnix, componentThrottled, uid), nil
   652  }
   653  
   654  // StartVReplication returns a statement to start the replication.
   655  func StartVReplication(uid uint32) string {
   656  	return fmt.Sprintf(
   657  		"update _vt.vreplication set state='%v', stop_pos=NULL where id=%v",
   658  		BlpRunning, uid)
   659  }
   660  
   661  // StartVReplicationUntil returns a statement to start the replication with a stop position.
   662  func StartVReplicationUntil(uid uint32, pos string) string {
   663  	return fmt.Sprintf(
   664  		"update _vt.vreplication set state='%v', stop_pos=%v where id=%v",
   665  		BlpRunning, encodeString(pos), uid)
   666  }
   667  
   668  // StopVReplication returns a statement to stop the replication.
   669  func StopVReplication(uid uint32, message string) string {
   670  	return fmt.Sprintf(
   671  		"update _vt.vreplication set state='%v', message=%v where id=%v",
   672  		BlpStopped, encodeString(MessageTruncate(message)), uid)
   673  }
   674  
   675  // DeleteVReplication returns a statement to delete the replication.
   676  func DeleteVReplication(uid uint32) string {
   677  	return fmt.Sprintf("delete from _vt.vreplication where id=%v", uid)
   678  }
   679  
   680  // MessageTruncate truncates the message string to a safe length.
   681  func MessageTruncate(msg string) string {
   682  	// message length is 1000 bytes.
   683  	return LimitString(msg, 950)
   684  }
   685  
   686  func encodeString(in string) string {
   687  	buf := bytes.NewBuffer(nil)
   688  	sqltypes.NewVarChar(in).EncodeSQL(buf)
   689  	return buf.String()
   690  }
   691  
   692  // ReadVReplicationPos returns a statement to query the gtid for a
   693  // given stream from the _vt.vreplication table.
   694  func ReadVReplicationPos(index uint32) string {
   695  	return fmt.Sprintf("select pos from _vt.vreplication where id=%v", index)
   696  }
   697  
   698  // ReadVReplicationStatus returns a statement to query the status fields for a
   699  // given stream from the _vt.vreplication table.
   700  func ReadVReplicationStatus(index uint32) string {
   701  	return fmt.Sprintf("select pos, state, message from _vt.vreplication where id=%v", index)
   702  }
   703  
   704  // MysqlUncompress will uncompress a binary string in the format stored by mysql's compress() function
   705  // The first four bytes represent the size of the original string passed to compress()
   706  // Remaining part is the compressed string using zlib, which we uncompress here using golang's zlib library
   707  func MysqlUncompress(input string) []byte {
   708  	// consistency check
   709  	inputBytes := []byte(input)
   710  	if len(inputBytes) < 5 {
   711  		return nil
   712  	}
   713  
   714  	// determine length
   715  	dataLength := uint32(inputBytes[0]) + uint32(inputBytes[1])<<8 + uint32(inputBytes[2])<<16 + uint32(inputBytes[3])<<24
   716  	dataLengthBytes := make([]byte, 4)
   717  	binary.LittleEndian.PutUint32(dataLengthBytes, dataLength)
   718  	dataLength = binary.LittleEndian.Uint32(dataLengthBytes)
   719  
   720  	// uncompress using zlib
   721  	inputData := inputBytes[4:]
   722  	inputDataBuf := bytes.NewBuffer(inputData)
   723  	reader, err := zlib.NewReader(inputDataBuf)
   724  	if err != nil {
   725  		return nil
   726  	}
   727  	var outputBytes bytes.Buffer
   728  	if _, err := io.Copy(&outputBytes, reader); err != nil {
   729  		return nil
   730  	}
   731  	if outputBytes.Len() == 0 {
   732  		return nil
   733  	}
   734  	if dataLength != uint32(outputBytes.Len()) { // double check that the stored and uncompressed lengths match
   735  		return nil
   736  	}
   737  	return outputBytes.Bytes()
   738  }
   739  
   740  // DecodePosition attempts to uncompress the passed value first and if it fails tries to decode it as a valid GTID
   741  func DecodePosition(gtid string) (mysql.Position, error) {
   742  	b := MysqlUncompress(gtid)
   743  	if b != nil {
   744  		gtid = string(b)
   745  	}
   746  	return mysql.DecodePosition(gtid)
   747  }
   748  
   749  // StatsHistoryRecord is used to store a Message with timestamp
   750  type StatsHistoryRecord struct {
   751  	Time    time.Time
   752  	Message string
   753  }
   754  
   755  // IsDuplicate implements history.Deduplicable
   756  func (r *StatsHistoryRecord) IsDuplicate(other any) bool {
   757  	return false
   758  }
   759  
   760  const binlogPlayerProtocolFlagName = "binlog_player_protocol"
   761  
   762  // SetProtocol is a helper function to set the binlogplayer --binlog_player_protocol
   763  // flag value for tests. If successful, it returns a function that, when called,
   764  // returns the flag to its previous value.
   765  //
   766  // Note that because this variable is bound to a flag, the effects of this
   767  // function are global, not scoped to the calling test-case. Therefore, it should
   768  // not be used in conjunction with t.Parallel.
   769  func SetProtocol(name string, protocol string) (reset func()) {
   770  	var tmp []string
   771  	tmp, os.Args = os.Args[:], []string{name}
   772  	defer func() { os.Args = tmp }()
   773  
   774  	servenv.OnParseFor(name, func(fs *pflag.FlagSet) {
   775  		if fs.Lookup(binlogPlayerProtocolFlagName) != nil {
   776  			return
   777  		}
   778  
   779  		registerFlags(fs)
   780  	})
   781  	servenv.ParseFlags(name)
   782  
   783  	switch oldVal, err := pflag.CommandLine.GetString(binlogPlayerProtocolFlagName); err {
   784  	case nil:
   785  		reset = func() { SetProtocol(name, oldVal) }
   786  	default:
   787  		log.Errorf("failed to get string value for flag %q: %v", binlogPlayerProtocolFlagName, err)
   788  		reset = func() {}
   789  	}
   790  
   791  	if err := pflag.Set(binlogPlayerProtocolFlagName, protocol); err != nil {
   792  		msg := "failed to set flag %q to %q: %v"
   793  		log.Errorf(msg, binlogPlayerProtocolFlagName, protocol, err)
   794  		reset = func() {}
   795  	}
   796  
   797  	return reset
   798  }