vitess.io/vitess@v0.16.2/go/vt/mysqlctl/fakemysqldaemon/fakemysqldaemon.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 fakemysqldaemon
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"context"
    27  
    28  	"vitess.io/vitess/go/mysql"
    29  	"vitess.io/vitess/go/mysql/fakesqldb"
    30  	"vitess.io/vitess/go/sqltypes"
    31  	"vitess.io/vitess/go/sync2"
    32  	"vitess.io/vitess/go/vt/dbconnpool"
    33  	"vitess.io/vitess/go/vt/mysqlctl"
    34  	"vitess.io/vitess/go/vt/mysqlctl/tmutils"
    35  
    36  	querypb "vitess.io/vitess/go/vt/proto/query"
    37  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    38  )
    39  
    40  // FakeMysqlDaemon implements MysqlDaemon and allows the user to fake
    41  // everything.
    42  type FakeMysqlDaemon struct {
    43  	mu sync.Mutex
    44  	// db is the fake SQL DB we may use for some queries.
    45  	db *fakesqldb.DB
    46  
    47  	// appPool is set if db is set.
    48  	appPool *dbconnpool.ConnectionPool
    49  
    50  	// Running is used by Start / Shutdown
    51  	Running bool
    52  
    53  	// StartupTime is used to simulate mysqlds that take some time to respond
    54  	// to a "start" command. It is used by Start.
    55  	StartupTime time.Duration
    56  
    57  	// ShutdownTime is used to simulate mysqlds that take some time to respond
    58  	// to a "stop" request (i.e. a wedged systemd unit). It is used by Shutdown.
    59  	ShutdownTime time.Duration
    60  
    61  	// MysqlPort will be returned by GetMysqlPort(). Set to -1 to
    62  	// return an error.
    63  	MysqlPort sync2.AtomicInt32
    64  
    65  	// Replicating is updated when calling StartReplication / StopReplication
    66  	// (it is not used at all when calling ReplicationStatus, it is the
    67  	// test owner responsibility to have these two match)
    68  	Replicating bool
    69  
    70  	// IOThreadRunning is always true except in one testcase
    71  	// where we want to test error handling during SetReplicationSource
    72  	IOThreadRunning bool
    73  
    74  	// CurrentPrimaryPosition is returned by PrimaryPosition
    75  	// and ReplicationStatus
    76  	CurrentPrimaryPosition mysql.Position
    77  
    78  	// CurrentSourceFilePosition is used to determine the executed file based positioning of the replication source.
    79  	CurrentSourceFilePosition mysql.Position
    80  
    81  	// ReplicationStatusError is used by ReplicationStatus
    82  	ReplicationStatusError error
    83  
    84  	// StartReplicationError is used by StartReplication
    85  	StartReplicationError error
    86  
    87  	// PromoteLag is the time for which Promote will stall
    88  	PromoteLag time.Duration
    89  
    90  	// PrimaryStatusError is used by PrimaryStatus
    91  	PrimaryStatusError error
    92  
    93  	// CurrentSourceHost is returned by ReplicationStatus
    94  	CurrentSourceHost string
    95  
    96  	// CurrentSourcePort is returned by ReplicationStatus
    97  	CurrentSourcePort int
    98  
    99  	// ReplicationLagSeconds is returned by ReplicationStatus
   100  	ReplicationLagSeconds uint
   101  
   102  	// ReadOnly is the current value of the flag
   103  	ReadOnly bool
   104  
   105  	// SuperReadOnly is the current value of the flag
   106  	SuperReadOnly bool
   107  
   108  	// SetReplicationPositionPos is matched against the input of SetReplicationPosition.
   109  	// If it doesn't match, SetReplicationPosition will return an error.
   110  	SetReplicationPositionPos mysql.Position
   111  
   112  	// StartReplicationUntilAfterPos is matched against the input
   113  	StartReplicationUntilAfterPos mysql.Position
   114  
   115  	// SetReplicationSourceInputs are matched against the input of SetReplicationSource
   116  	// (as "%v:%v"). If all of them don't match, SetReplicationSource will return an error.
   117  	SetReplicationSourceInputs []string
   118  
   119  	// SetReplicationSourceError is used by SetReplicationSource
   120  	SetReplicationSourceError error
   121  
   122  	// WaitPrimaryPositions is checked by WaitSourcePos, if the value is found
   123  	// in it, then the function returns nil, else the function returns an error
   124  	WaitPrimaryPositions []mysql.Position
   125  
   126  	// PromoteResult is returned by Promote
   127  	PromoteResult mysql.Position
   128  
   129  	// PromoteError is used by Promote
   130  	PromoteError error
   131  
   132  	// SchemaFunc provides the return value for GetSchema.
   133  	// If not defined, the "Schema" field will be used instead, see below.
   134  	SchemaFunc func() (*tabletmanagerdatapb.SchemaDefinition, error)
   135  
   136  	// Schema will be returned by GetSchema. If nil we'll
   137  	// return an error.
   138  	Schema *tabletmanagerdatapb.SchemaDefinition
   139  
   140  	// PreflightSchemaChangeResult will be returned by PreflightSchemaChange.
   141  	// If nil we'll return an error.
   142  	PreflightSchemaChangeResult []*tabletmanagerdatapb.SchemaChangeResult
   143  
   144  	// ApplySchemaChangeResult will be returned by ApplySchemaChange.
   145  	// If nil we'll return an error.
   146  	ApplySchemaChangeResult *tabletmanagerdatapb.SchemaChangeResult
   147  
   148  	// ExpectedExecuteSuperQueryList is what we expect
   149  	// ExecuteSuperQueryList to be called with. If it doesn't
   150  	// match, ExecuteSuperQueryList will return an error.
   151  	// Note each string is just a substring if it begins with SUB,
   152  	// so we support partial queries (useful when queries contain
   153  	// data fields like timestamps)
   154  	ExpectedExecuteSuperQueryList []string
   155  
   156  	// ExpectedExecuteSuperQueryCurrent is the current index of the queries
   157  	// we expect
   158  	ExpectedExecuteSuperQueryCurrent int
   159  
   160  	// FetchSuperQueryResults is used by FetchSuperQuery
   161  	FetchSuperQueryMap map[string]*sqltypes.Result
   162  
   163  	// BinlogPlayerEnabled is used by {Enable,Disable}BinlogPlayer
   164  	BinlogPlayerEnabled sync2.AtomicBool
   165  
   166  	// SemiSyncPrimaryEnabled represents the state of rpl_semi_sync_master_enabled.
   167  	SemiSyncPrimaryEnabled bool
   168  	// SemiSyncReplicaEnabled represents the state of rpl_semi_sync_slave_enabled.
   169  	SemiSyncReplicaEnabled bool
   170  
   171  	// TimeoutHook is a func that can be called at the beginning of any method to fake a timeout.
   172  	// all a test needs to do is make it { return context.DeadlineExceeded }
   173  	TimeoutHook func() error
   174  }
   175  
   176  // NewFakeMysqlDaemon returns a FakeMysqlDaemon where mysqld appears
   177  // to be running, based on a fakesqldb.DB.
   178  // 'db' can be nil if the test doesn't use a database at all.
   179  func NewFakeMysqlDaemon(db *fakesqldb.DB) *FakeMysqlDaemon {
   180  	result := &FakeMysqlDaemon{
   181  		db:              db,
   182  		Running:         true,
   183  		IOThreadRunning: true,
   184  	}
   185  	if db != nil {
   186  		result.appPool = dbconnpool.NewConnectionPool("AppConnPool", 5, time.Minute, 0, 0)
   187  		result.appPool.Open(db.ConnParams())
   188  	}
   189  	return result
   190  }
   191  
   192  // Start is part of the MysqlDaemon interface
   193  func (fmd *FakeMysqlDaemon) Start(ctx context.Context, cnf *mysqlctl.Mycnf, mysqldArgs ...string) error {
   194  	if fmd.Running {
   195  		return fmt.Errorf("fake mysql daemon already running")
   196  	}
   197  
   198  	if fmd.StartupTime > 0 {
   199  		select {
   200  		case <-time.After(fmd.StartupTime):
   201  		case <-ctx.Done():
   202  			return ctx.Err()
   203  		}
   204  	}
   205  
   206  	fmd.Running = true
   207  	return nil
   208  }
   209  
   210  // Shutdown is part of the MysqlDaemon interface
   211  func (fmd *FakeMysqlDaemon) Shutdown(ctx context.Context, cnf *mysqlctl.Mycnf, waitForMysqld bool) error {
   212  	if !fmd.Running {
   213  		return fmt.Errorf("fake mysql daemon not running")
   214  	}
   215  
   216  	if fmd.ShutdownTime > 0 {
   217  		select {
   218  		case <-time.After(fmd.ShutdownTime):
   219  		case <-ctx.Done():
   220  			return ctx.Err()
   221  		}
   222  	}
   223  
   224  	fmd.Running = false
   225  	return nil
   226  }
   227  
   228  // RunMysqlUpgrade is part of the MysqlDaemon interface
   229  func (fmd *FakeMysqlDaemon) RunMysqlUpgrade() error {
   230  	return nil
   231  }
   232  
   233  // ReinitConfig is part of the MysqlDaemon interface
   234  func (fmd *FakeMysqlDaemon) ReinitConfig(ctx context.Context, cnf *mysqlctl.Mycnf) error {
   235  	return nil
   236  }
   237  
   238  // RefreshConfig is part of the MysqlDaemon interface
   239  func (fmd *FakeMysqlDaemon) RefreshConfig(ctx context.Context, cnf *mysqlctl.Mycnf) error {
   240  	return nil
   241  }
   242  
   243  // Wait is part of the MysqlDaemon interface.
   244  func (fmd *FakeMysqlDaemon) Wait(ctx context.Context, cnf *mysqlctl.Mycnf) error {
   245  	return nil
   246  }
   247  
   248  // GetMysqlPort is part of the MysqlDaemon interface
   249  func (fmd *FakeMysqlDaemon) GetMysqlPort() (int32, error) {
   250  	if fmd.MysqlPort.Get() == -1 {
   251  		return 0, fmt.Errorf("FakeMysqlDaemon.GetMysqlPort returns an error")
   252  	}
   253  	return fmd.MysqlPort.Get(), nil
   254  }
   255  
   256  // GetServerID is part of the MysqlDaemon interface
   257  func (fmd *FakeMysqlDaemon) GetServerID(ctx context.Context) (uint32, error) {
   258  	return 1, nil
   259  }
   260  
   261  // GetServerUUID is part of the MysqlDaemon interface
   262  func (fmd *FakeMysqlDaemon) GetServerUUID(ctx context.Context) (string, error) {
   263  	return "000000", nil
   264  }
   265  
   266  // CurrentPrimaryPositionLocked is thread-safe
   267  func (fmd *FakeMysqlDaemon) CurrentPrimaryPositionLocked(pos mysql.Position) {
   268  	fmd.mu.Lock()
   269  	defer fmd.mu.Unlock()
   270  	fmd.CurrentPrimaryPosition = pos
   271  }
   272  
   273  // ReplicationStatus is part of the MysqlDaemon interface
   274  func (fmd *FakeMysqlDaemon) ReplicationStatus() (mysql.ReplicationStatus, error) {
   275  	if fmd.ReplicationStatusError != nil {
   276  		return mysql.ReplicationStatus{}, fmd.ReplicationStatusError
   277  	}
   278  	fmd.mu.Lock()
   279  	defer fmd.mu.Unlock()
   280  	return mysql.ReplicationStatus{
   281  		Position:                               fmd.CurrentPrimaryPosition,
   282  		FilePosition:                           fmd.CurrentSourceFilePosition,
   283  		RelayLogSourceBinlogEquivalentPosition: fmd.CurrentSourceFilePosition,
   284  		ReplicationLagSeconds:                  fmd.ReplicationLagSeconds,
   285  		// implemented as AND to avoid changing all tests that were
   286  		// previously using Replicating = false
   287  		IOState:    mysql.ReplicationStatusToState(fmt.Sprintf("%v", fmd.Replicating && fmd.IOThreadRunning)),
   288  		SQLState:   mysql.ReplicationStatusToState(fmt.Sprintf("%v", fmd.Replicating)),
   289  		SourceHost: fmd.CurrentSourceHost,
   290  		SourcePort: fmd.CurrentSourcePort,
   291  	}, nil
   292  }
   293  
   294  // PrimaryStatus is part of the MysqlDaemon interface
   295  func (fmd *FakeMysqlDaemon) PrimaryStatus(ctx context.Context) (mysql.PrimaryStatus, error) {
   296  	if fmd.PrimaryStatusError != nil {
   297  		return mysql.PrimaryStatus{}, fmd.PrimaryStatusError
   298  	}
   299  	return mysql.PrimaryStatus{
   300  		Position:     fmd.CurrentPrimaryPosition,
   301  		FilePosition: fmd.CurrentSourceFilePosition,
   302  	}, nil
   303  }
   304  
   305  // GetGTIDPurged is part of the MysqlDaemon interface
   306  func (fmd *FakeMysqlDaemon) GetGTIDPurged(ctx context.Context) (mysql.Position, error) {
   307  	return mysql.Position{}, nil
   308  }
   309  
   310  // ResetReplication is part of the MysqlDaemon interface.
   311  func (fmd *FakeMysqlDaemon) ResetReplication(ctx context.Context) error {
   312  	return fmd.ExecuteSuperQueryList(ctx, []string{
   313  		"FAKE RESET ALL REPLICATION",
   314  	})
   315  }
   316  
   317  // ResetReplicationParameters is part of the MysqlDaemon interface.
   318  func (fmd *FakeMysqlDaemon) ResetReplicationParameters(ctx context.Context) error {
   319  	return fmd.ExecuteSuperQueryList(ctx, []string{
   320  		"FAKE RESET REPLICA ALL",
   321  	})
   322  }
   323  
   324  // GetBinlogInformation is part of the MysqlDaemon interface.
   325  func (fmd *FakeMysqlDaemon) GetBinlogInformation(ctx context.Context) (binlogFormat string, logEnabled bool, logReplicaUpdate bool, binlogRowImage string, err error) {
   326  	return "ROW", true, true, "FULL", fmd.ExecuteSuperQueryList(ctx, []string{
   327  		"FAKE select @@global",
   328  	})
   329  }
   330  
   331  // GetGTIDMode is part of the MysqlDaemon interface.
   332  func (fmd *FakeMysqlDaemon) GetGTIDMode(ctx context.Context) (gtidMode string, err error) {
   333  	return "ON", fmd.ExecuteSuperQueryList(ctx, []string{
   334  		"FAKE select @@global",
   335  	})
   336  }
   337  
   338  // FlushBinaryLogs is part of the MysqlDaemon interface.
   339  func (fmd *FakeMysqlDaemon) FlushBinaryLogs(ctx context.Context) (err error) {
   340  	return fmd.ExecuteSuperQueryList(ctx, []string{
   341  		"FAKE FLUSH BINARY LOGS",
   342  	})
   343  }
   344  
   345  // GetBinaryLogs is part of the MysqlDaemon interface.
   346  func (fmd *FakeMysqlDaemon) GetBinaryLogs(ctx context.Context) (binaryLogs []string, err error) {
   347  	return []string{}, fmd.ExecuteSuperQueryList(ctx, []string{
   348  		"FAKE SHOW BINARY LOGS",
   349  	})
   350  }
   351  
   352  // GetPreviousGTIDs is part of the MysqlDaemon interface.
   353  func (fmd *FakeMysqlDaemon) GetPreviousGTIDs(ctx context.Context, binlog string) (previousGtids string, err error) {
   354  	return "", fmd.ExecuteSuperQueryList(ctx, []string{
   355  		fmt.Sprintf("FAKE SHOW BINLOG EVENTS IN '%s' LIMIT 2", binlog),
   356  	})
   357  }
   358  
   359  // PrimaryPosition is part of the MysqlDaemon interface
   360  func (fmd *FakeMysqlDaemon) PrimaryPosition() (mysql.Position, error) {
   361  	return fmd.CurrentPrimaryPosition, nil
   362  }
   363  
   364  // IsReadOnly is part of the MysqlDaemon interface
   365  func (fmd *FakeMysqlDaemon) IsReadOnly() (bool, error) {
   366  	return fmd.ReadOnly, nil
   367  }
   368  
   369  // SetReadOnly is part of the MysqlDaemon interface
   370  func (fmd *FakeMysqlDaemon) SetReadOnly(on bool) error {
   371  	fmd.ReadOnly = on
   372  	return nil
   373  }
   374  
   375  // SetSuperReadOnly is part of the MysqlDaemon interface
   376  func (fmd *FakeMysqlDaemon) SetSuperReadOnly(on bool) error {
   377  	fmd.SuperReadOnly = on
   378  	fmd.ReadOnly = on
   379  	return nil
   380  }
   381  
   382  // StartReplication is part of the MysqlDaemon interface.
   383  func (fmd *FakeMysqlDaemon) StartReplication(hookExtraEnv map[string]string) error {
   384  	if fmd.StartReplicationError != nil {
   385  		return fmd.StartReplicationError
   386  	}
   387  	return fmd.ExecuteSuperQueryList(context.Background(), []string{
   388  		"START SLAVE",
   389  	})
   390  }
   391  
   392  // RestartReplication is part of the MysqlDaemon interface.
   393  func (fmd *FakeMysqlDaemon) RestartReplication(hookExtraEnv map[string]string) error {
   394  	return fmd.ExecuteSuperQueryList(context.Background(), []string{
   395  		"STOP SLAVE",
   396  		"RESET SLAVE",
   397  		"START SLAVE",
   398  	})
   399  }
   400  
   401  // StartReplicationUntilAfter is part of the MysqlDaemon interface.
   402  func (fmd *FakeMysqlDaemon) StartReplicationUntilAfter(ctx context.Context, pos mysql.Position) error {
   403  	if !reflect.DeepEqual(fmd.StartReplicationUntilAfterPos, pos) {
   404  		return fmt.Errorf("wrong pos for StartReplicationUntilAfter: expected %v got %v", fmd.SetReplicationPositionPos, pos)
   405  	}
   406  
   407  	return fmd.ExecuteSuperQueryList(context.Background(), []string{
   408  		"START SLAVE UNTIL AFTER",
   409  	})
   410  }
   411  
   412  // StopReplication is part of the MysqlDaemon interface.
   413  func (fmd *FakeMysqlDaemon) StopReplication(hookExtraEnv map[string]string) error {
   414  	return fmd.ExecuteSuperQueryList(context.Background(), []string{
   415  		"STOP SLAVE",
   416  	})
   417  }
   418  
   419  // StopIOThread is part of the MysqlDaemon interface.
   420  func (fmd *FakeMysqlDaemon) StopIOThread(ctx context.Context) error {
   421  	return fmd.ExecuteSuperQueryList(context.Background(), []string{
   422  		"STOP SLAVE IO_THREAD",
   423  	})
   424  }
   425  
   426  // SetReplicationPosition is part of the MysqlDaemon interface.
   427  func (fmd *FakeMysqlDaemon) SetReplicationPosition(ctx context.Context, pos mysql.Position) error {
   428  	if !reflect.DeepEqual(fmd.SetReplicationPositionPos, pos) {
   429  		return fmt.Errorf("wrong pos for SetReplicationPosition: expected %v got %v", fmd.SetReplicationPositionPos, pos)
   430  	}
   431  	return fmd.ExecuteSuperQueryList(ctx, []string{
   432  		"FAKE SET SLAVE POSITION",
   433  	})
   434  }
   435  
   436  // SetReplicationSource is part of the MysqlDaemon interface.
   437  func (fmd *FakeMysqlDaemon) SetReplicationSource(ctx context.Context, host string, port int, stopReplicationBefore bool, startReplicationAfter bool) error {
   438  	input := fmt.Sprintf("%v:%v", host, port)
   439  	found := false
   440  	for _, sourceInput := range fmd.SetReplicationSourceInputs {
   441  		if sourceInput == input {
   442  			found = true
   443  		}
   444  	}
   445  	if !found {
   446  		return fmt.Errorf("wrong input for SetReplicationSourceCommands: expected a value in %v got %v", fmd.SetReplicationSourceInputs, input)
   447  	}
   448  	if fmd.SetReplicationSourceError != nil {
   449  		return fmd.SetReplicationSourceError
   450  	}
   451  	cmds := []string{}
   452  	if stopReplicationBefore {
   453  		cmds = append(cmds, "STOP SLAVE")
   454  	}
   455  	cmds = append(cmds, "RESET SLAVE ALL")
   456  	cmds = append(cmds, "FAKE SET MASTER")
   457  	if startReplicationAfter {
   458  		cmds = append(cmds, "START SLAVE")
   459  	}
   460  	return fmd.ExecuteSuperQueryList(ctx, cmds)
   461  }
   462  
   463  // WaitForReparentJournal is part of the MysqlDaemon interface
   464  func (fmd *FakeMysqlDaemon) WaitForReparentJournal(ctx context.Context, timeCreatedNS int64) error {
   465  	return nil
   466  }
   467  
   468  // WaitSourcePos is part of the MysqlDaemon interface
   469  func (fmd *FakeMysqlDaemon) WaitSourcePos(_ context.Context, pos mysql.Position) error {
   470  	if fmd.TimeoutHook != nil {
   471  		return fmd.TimeoutHook()
   472  	}
   473  	for _, position := range fmd.WaitPrimaryPositions {
   474  		if reflect.DeepEqual(position, pos) {
   475  			return nil
   476  		}
   477  	}
   478  	return fmt.Errorf("wrong input for WaitSourcePos: expected a value in %v got %v", fmd.WaitPrimaryPositions, pos)
   479  }
   480  
   481  // Promote is part of the MysqlDaemon interface
   482  func (fmd *FakeMysqlDaemon) Promote(hookExtraEnv map[string]string) (mysql.Position, error) {
   483  	if fmd.PromoteLag > 0 {
   484  		time.Sleep(fmd.PromoteLag)
   485  	}
   486  	if fmd.PromoteError != nil {
   487  		return mysql.Position{}, fmd.PromoteError
   488  	}
   489  	return fmd.PromoteResult, nil
   490  }
   491  
   492  // ExecuteSuperQueryList is part of the MysqlDaemon interface
   493  func (fmd *FakeMysqlDaemon) ExecuteSuperQueryList(ctx context.Context, queryList []string) error {
   494  	for _, query := range queryList {
   495  		// test we still have a query to compare
   496  		if fmd.ExpectedExecuteSuperQueryCurrent >= len(fmd.ExpectedExecuteSuperQueryList) {
   497  			return fmt.Errorf("unexpected extra query in ExecuteSuperQueryList: %v", query)
   498  		}
   499  
   500  		// compare the query
   501  		expected := fmd.ExpectedExecuteSuperQueryList[fmd.ExpectedExecuteSuperQueryCurrent]
   502  		fmd.ExpectedExecuteSuperQueryCurrent++
   503  		if strings.HasPrefix(expected, "SUB") {
   504  			// remove the SUB from the expected,
   505  			// and truncate the query to length(expected)
   506  			expected = expected[3:]
   507  			if len(query) > len(expected) {
   508  				query = query[:len(expected)]
   509  			}
   510  		}
   511  		if expected != query {
   512  			return fmt.Errorf("wrong query for ExecuteSuperQueryList: expected %v got %v", expected, query)
   513  		}
   514  
   515  		// intercept some queries to update our status
   516  		switch query {
   517  		case "START SLAVE":
   518  			fmd.Replicating = true
   519  		case "STOP SLAVE":
   520  			fmd.Replicating = false
   521  		}
   522  	}
   523  	return nil
   524  }
   525  
   526  // FetchSuperQuery returns the results from the map, if any
   527  func (fmd *FakeMysqlDaemon) FetchSuperQuery(ctx context.Context, query string) (*sqltypes.Result, error) {
   528  	if fmd.FetchSuperQueryMap == nil {
   529  		return nil, fmt.Errorf("unexpected query: %v", query)
   530  	}
   531  
   532  	qr, ok := fmd.FetchSuperQueryMap[query]
   533  	if !ok {
   534  		return nil, fmt.Errorf("unexpected query: %v", query)
   535  	}
   536  	return qr, nil
   537  }
   538  
   539  // EnableBinlogPlayback is part of the MysqlDaemon interface
   540  func (fmd *FakeMysqlDaemon) EnableBinlogPlayback() error {
   541  	fmd.BinlogPlayerEnabled.Set(true)
   542  	return nil
   543  }
   544  
   545  // DisableBinlogPlayback disable playback of binlog events
   546  func (fmd *FakeMysqlDaemon) DisableBinlogPlayback() error {
   547  	fmd.BinlogPlayerEnabled.Set(false)
   548  	return nil
   549  }
   550  
   551  // Close is part of the MysqlDaemon interface
   552  func (fmd *FakeMysqlDaemon) Close() {
   553  	if fmd.appPool != nil {
   554  		fmd.appPool.Close()
   555  	}
   556  }
   557  
   558  // CheckSuperQueryList returns an error if all the queries we expected
   559  // haven't been seen.
   560  func (fmd *FakeMysqlDaemon) CheckSuperQueryList() error {
   561  	if fmd.ExpectedExecuteSuperQueryCurrent != len(fmd.ExpectedExecuteSuperQueryList) {
   562  		return fmt.Errorf("SuperQueryList wasn't consumed, saw %v queries, was expecting %v", fmd.ExpectedExecuteSuperQueryCurrent, len(fmd.ExpectedExecuteSuperQueryList))
   563  	}
   564  	return nil
   565  }
   566  
   567  // GetSchema is part of the MysqlDaemon interface
   568  func (fmd *FakeMysqlDaemon) GetSchema(ctx context.Context, dbName string, request *tabletmanagerdatapb.GetSchemaRequest) (*tabletmanagerdatapb.SchemaDefinition, error) {
   569  	if fmd.SchemaFunc != nil {
   570  		return fmd.SchemaFunc()
   571  	}
   572  	if fmd.Schema == nil {
   573  		return nil, fmt.Errorf("no schema defined")
   574  	}
   575  	return tmutils.FilterTables(fmd.Schema, request.Tables, request.ExcludeTables, request.IncludeViews)
   576  }
   577  
   578  // GetColumns is part of the MysqlDaemon interface
   579  func (fmd *FakeMysqlDaemon) GetColumns(ctx context.Context, dbName, table string) ([]*querypb.Field, []string, error) {
   580  	return []*querypb.Field{}, []string{}, nil
   581  }
   582  
   583  // GetPrimaryKeyColumns is part of the MysqlDaemon interface
   584  func (fmd *FakeMysqlDaemon) GetPrimaryKeyColumns(ctx context.Context, dbName, table string) ([]string, error) {
   585  	return []string{}, nil
   586  }
   587  
   588  // GetPrimaryKeyEquivalentColumns is part of the MysqlDaemon interface
   589  func (fmd *FakeMysqlDaemon) GetPrimaryKeyEquivalentColumns(ctx context.Context, dbName, table string) ([]string, error) {
   590  	return []string{}, nil
   591  }
   592  
   593  // PreflightSchemaChange is part of the MysqlDaemon interface
   594  func (fmd *FakeMysqlDaemon) PreflightSchemaChange(ctx context.Context, dbName string, changes []string) ([]*tabletmanagerdatapb.SchemaChangeResult, error) {
   595  	if fmd.PreflightSchemaChangeResult == nil {
   596  		return nil, fmt.Errorf("no preflight result defined")
   597  	}
   598  	return fmd.PreflightSchemaChangeResult, nil
   599  }
   600  
   601  // ApplySchemaChange is part of the MysqlDaemon interface
   602  func (fmd *FakeMysqlDaemon) ApplySchemaChange(ctx context.Context, dbName string, change *tmutils.SchemaChange) (*tabletmanagerdatapb.SchemaChangeResult, error) {
   603  	beforeSchema, err := fmd.SchemaFunc()
   604  	if err != nil {
   605  		return nil, err
   606  	}
   607  
   608  	dbaCon, err := fmd.GetDbaConnection(ctx)
   609  	if err != nil {
   610  		return nil, err
   611  	}
   612  	if err = fmd.db.HandleQuery(dbaCon.Conn, change.SQL, func(*sqltypes.Result) error { return nil }); err != nil {
   613  		return nil, err
   614  	}
   615  
   616  	afterSchema, err := fmd.SchemaFunc()
   617  	if err != nil {
   618  		return nil, err
   619  	}
   620  
   621  	return &tabletmanagerdatapb.SchemaChangeResult{
   622  		BeforeSchema: beforeSchema,
   623  		AfterSchema:  afterSchema}, nil
   624  }
   625  
   626  // GetAppConnection is part of the MysqlDaemon interface.
   627  func (fmd *FakeMysqlDaemon) GetAppConnection(ctx context.Context) (*dbconnpool.PooledDBConnection, error) {
   628  	return fmd.appPool.Get(ctx)
   629  }
   630  
   631  // GetDbaConnection is part of the MysqlDaemon interface.
   632  func (fmd *FakeMysqlDaemon) GetDbaConnection(ctx context.Context) (*dbconnpool.DBConnection, error) {
   633  	return dbconnpool.NewDBConnection(ctx, fmd.db.ConnParams())
   634  }
   635  
   636  // GetAllPrivsConnection is part of the MysqlDaemon interface.
   637  func (fmd *FakeMysqlDaemon) GetAllPrivsConnection(ctx context.Context) (*dbconnpool.DBConnection, error) {
   638  	return dbconnpool.NewDBConnection(ctx, fmd.db.ConnParams())
   639  }
   640  
   641  // SetSemiSyncEnabled is part of the MysqlDaemon interface.
   642  func (fmd *FakeMysqlDaemon) SetSemiSyncEnabled(primary, replica bool) error {
   643  	fmd.SemiSyncPrimaryEnabled = primary
   644  	fmd.SemiSyncReplicaEnabled = replica
   645  	return nil
   646  }
   647  
   648  // SemiSyncEnabled is part of the MysqlDaemon interface.
   649  func (fmd *FakeMysqlDaemon) SemiSyncEnabled() (primary, replica bool) {
   650  	return fmd.SemiSyncPrimaryEnabled, fmd.SemiSyncReplicaEnabled
   651  }
   652  
   653  // SemiSyncStatus is part of the MysqlDaemon interface.
   654  func (fmd *FakeMysqlDaemon) SemiSyncStatus() (bool, bool) {
   655  	// The fake assumes the status worked.
   656  	if fmd.SemiSyncPrimaryEnabled {
   657  		return true, false
   658  	}
   659  	return false, fmd.SemiSyncReplicaEnabled
   660  }
   661  
   662  // SemiSyncClients is part of the MysqlDaemon interface.
   663  func (fmd *FakeMysqlDaemon) SemiSyncClients() uint32 {
   664  	return 0
   665  }
   666  
   667  // SemiSyncSettings is part of the MysqlDaemon interface.
   668  func (fmd *FakeMysqlDaemon) SemiSyncSettings() (timeout uint64, numReplicas uint32) {
   669  	return 10000000, 1
   670  }
   671  
   672  // SemiSyncReplicationStatus is part of the MysqlDaemon interface.
   673  func (fmd *FakeMysqlDaemon) SemiSyncReplicationStatus() (bool, error) {
   674  	// The fake assumes the status worked.
   675  	return fmd.SemiSyncReplicaEnabled, nil
   676  }
   677  
   678  // GetVersionString is part of the MysqlDeamon interface.
   679  func (fmd *FakeMysqlDaemon) GetVersionString() string {
   680  	return ""
   681  }
   682  
   683  // GetVersionComment is part of the MysqlDeamon interface.
   684  func (fmd *FakeMysqlDaemon) GetVersionComment(ctx context.Context) string {
   685  	return ""
   686  }