vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vreplication/vcopier_test.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 vreplication
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"vitess.io/vitess/go/vt/log"
    27  	"vitess.io/vitess/go/vt/mysqlctl"
    28  
    29  	"context"
    30  
    31  	"github.com/stretchr/testify/require"
    32  
    33  	"vitess.io/vitess/go/sqltypes"
    34  	"vitess.io/vitess/go/vt/binlog/binlogplayer"
    35  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    36  	qh "vitess.io/vitess/go/vt/vttablet/tabletmanager/vreplication/queryhistory"
    37  	"vitess.io/vitess/go/vt/vttablet/tabletserver/vstreamer"
    38  )
    39  
    40  type vcopierTestCase struct {
    41  	vreplicationExperimentalFlags     int64
    42  	vreplicationParallelInsertWorkers int
    43  }
    44  
    45  func commonVcopierTestCases() []vcopierTestCase {
    46  	return []vcopierTestCase{
    47  		// Default experimental flags.
    48  		{
    49  			vreplicationExperimentalFlags: vreplicationExperimentalFlags,
    50  		},
    51  		// Parallel bulk inserts enabled with 4 workers.
    52  		{
    53  			vreplicationExperimentalFlags:     vreplicationExperimentalFlags,
    54  			vreplicationParallelInsertWorkers: 4,
    55  		},
    56  	}
    57  }
    58  
    59  func testVcopierTestCases(t *testing.T, test func(*testing.T), cases []vcopierTestCase) {
    60  	oldVreplicationExperimentalFlags := vreplicationExperimentalFlags
    61  	oldVreplicationParallelInsertWorkers := vreplicationParallelInsertWorkers
    62  	// Extra reset at the end in case we return prematurely.
    63  	defer func() {
    64  		vreplicationExperimentalFlags = oldVreplicationExperimentalFlags
    65  		vreplicationParallelInsertWorkers = oldVreplicationParallelInsertWorkers
    66  	}()
    67  
    68  	for _, tc := range cases {
    69  		tc := tc // Avoid export loop bugs.
    70  		// Set test flags.
    71  		vreplicationExperimentalFlags = tc.vreplicationExperimentalFlags
    72  		vreplicationParallelInsertWorkers = tc.vreplicationParallelInsertWorkers
    73  		// Run test case.
    74  		t.Run(
    75  			fmt.Sprintf(
    76  				"vreplication_experimental_flags=%d,vreplication_parallel_insert_workers=%d",
    77  				tc.vreplicationExperimentalFlags, tc.vreplicationParallelInsertWorkers,
    78  			),
    79  			test,
    80  		)
    81  		// Reset.
    82  		vreplicationExperimentalFlags = oldVreplicationExperimentalFlags
    83  		vreplicationParallelInsertWorkers = oldVreplicationParallelInsertWorkers
    84  	}
    85  }
    86  
    87  func TestPlayerCopyCharPK(t *testing.T) {
    88  	testVcopierTestCases(t, testPlayerCopyCharPK, commonVcopierTestCases())
    89  }
    90  
    91  func testPlayerCopyCharPK(t *testing.T) {
    92  	defer deleteTablet(addTablet(100))
    93  
    94  	reset := vstreamer.AdjustPacketSize(1)
    95  	defer reset()
    96  
    97  	savedCopyPhaseDuration := copyPhaseDuration
    98  	// copyPhaseDuration should be low enough to have time to send one row.
    99  	copyPhaseDuration = 500 * time.Millisecond
   100  	defer func() { copyPhaseDuration = savedCopyPhaseDuration }()
   101  
   102  	savedWaitRetryTime := waitRetryTime
   103  	// waitRetry time should be very low to cause the wait loop to execute multipel times.
   104  	waitRetryTime = 10 * time.Millisecond
   105  	defer func() { waitRetryTime = savedWaitRetryTime }()
   106  
   107  	execStatements(t, []string{
   108  		"create table src(idc binary(2) , val int, primary key(idc))",
   109  		"insert into src values('a', 1), ('c', 2)",
   110  		fmt.Sprintf("create table %s.dst(idc binary(2), val int, primary key(idc))", vrepldb),
   111  	})
   112  	defer execStatements(t, []string{
   113  		"drop table src",
   114  		fmt.Sprintf("drop table %s.dst", vrepldb),
   115  	})
   116  	env.SchemaEngine.Reload(context.Background())
   117  
   118  	count := 0
   119  	vstreamRowsSendHook = func(ctx context.Context) {
   120  		defer func() { count++ }()
   121  		// Allow the first two calls to go through: field info and one row.
   122  		if count <= 1 {
   123  			return
   124  		}
   125  		// Insert a row with PK which is < the lastPK till now because of the utf8mb4 collation
   126  		execStatements(t, []string{
   127  			"update src set val = 3 where idc = 'a\000'",
   128  		})
   129  		// Wait for context to expire and then send the row.
   130  		// This will cause the copier to abort and go back to catchup mode.
   131  		<-ctx.Done()
   132  		// Do this no more than once.
   133  		vstreamRowsSendHook = nil
   134  	}
   135  
   136  	vstreamHook = func(context.Context) {
   137  		// Sleeping 50ms guarantees that the catchup wait loop executes multiple times.
   138  		// This is because waitRetryTime is set to 10ms.
   139  		time.Sleep(50 * time.Millisecond)
   140  		// Do this no more than once.
   141  		vstreamHook = nil
   142  	}
   143  
   144  	filter := &binlogdatapb.Filter{
   145  		Rules: []*binlogdatapb.Rule{{
   146  			Match:  "dst",
   147  			Filter: "select * from src",
   148  		}},
   149  	}
   150  
   151  	bls := &binlogdatapb.BinlogSource{
   152  		Keyspace: env.KeyspaceName,
   153  		Shard:    env.ShardName,
   154  		Filter:   filter,
   155  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
   156  	}
   157  
   158  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
   159  	qr, err := playerEngine.Exec(query)
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	defer func() {
   164  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
   165  		if _, err := playerEngine.Exec(query); err != nil {
   166  			t.Fatal(err)
   167  		}
   168  		expectDeleteQueries(t)
   169  	}()
   170  
   171  	expectNontxQueries(t, qh.Expect(
   172  		"/insert into _vt.vreplication",
   173  		"/update _vt.vreplication set message='Picked source tablet.*",
   174  		"/insert into _vt.copy_state",
   175  		"/update _vt.vreplication set state='Copying'",
   176  		"insert into dst(idc,val) values ('a\\0',1)",
   177  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"idc\\" type:BINARY} rows:{lengths:2 values:\\"a\\\\x00\\"}'.*`,
   178  		`update dst set val=3 where idc='a\0' and ('a\0') <= ('a\0')`,
   179  		"insert into dst(idc,val) values ('c\\0',2)",
   180  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"idc\\" type:BINARY} rows:{lengths:2 values:\\"c\\\\x00\\"}'.*`,
   181  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst",
   182  		"/update _vt.vreplication set state='Running",
   183  	))
   184  
   185  	expectData(t, "dst", [][]string{
   186  		{"a\000", "3"},
   187  		{"c\000", "2"},
   188  	})
   189  }
   190  
   191  // TestPlayerCopyVarcharPKCaseInsensitive tests the copy/catchup phase for a table with a varchar primary key
   192  // which is case insensitive.
   193  func TestPlayerCopyVarcharPKCaseInsensitive(t *testing.T) {
   194  	testVcopierTestCases(t, testPlayerCopyVarcharPKCaseInsensitive, commonVcopierTestCases())
   195  }
   196  
   197  func testPlayerCopyVarcharPKCaseInsensitive(t *testing.T) {
   198  	defer deleteTablet(addTablet(100))
   199  
   200  	// Set packet size low so that we send one row at a time.
   201  	reset := vstreamer.AdjustPacketSize(1)
   202  	defer reset()
   203  
   204  	savedCopyPhaseDuration := copyPhaseDuration
   205  	// copyPhaseDuration should be low enough to have time to send one row.
   206  	copyPhaseDuration = 500 * time.Millisecond
   207  	defer func() { copyPhaseDuration = savedCopyPhaseDuration }()
   208  
   209  	savedWaitRetryTime := waitRetryTime
   210  	// waitRetry time should be very low to cause the wait loop to execute multiple times.
   211  	waitRetryTime = 10 * time.Millisecond
   212  	defer func() { waitRetryTime = savedWaitRetryTime }()
   213  
   214  	execStatements(t, []string{
   215  		"create table src(idc varchar(20), val int, primary key(idc))",
   216  		"insert into src values('a', 1), ('c', 2)",
   217  		fmt.Sprintf("create table %s.dst(idc varchar(20), val int, primary key(idc))", vrepldb),
   218  	})
   219  	defer execStatements(t, []string{
   220  		"drop table src",
   221  		fmt.Sprintf("drop table %s.dst", vrepldb),
   222  	})
   223  	env.SchemaEngine.Reload(context.Background())
   224  
   225  	count := 0
   226  	vstreamRowsSendHook = func(ctx context.Context) {
   227  		defer func() { count++ }()
   228  		// Allow the first two calls to go through: field info and one row.
   229  		if count <= 1 {
   230  			return
   231  		}
   232  		// Insert a row with PK which is < the lastPK till now because of the utf8mb4 collation
   233  		execStatements(t, []string{
   234  			"insert into src values('B', 3)",
   235  		})
   236  		// Wait for context to expire and then send the row.
   237  		// This will cause the copier to abort and go back to catchup mode.
   238  		<-ctx.Done()
   239  		// Do this no more than once.
   240  		vstreamRowsSendHook = nil
   241  	}
   242  
   243  	vstreamHook = func(context.Context) {
   244  		// Sleeping 50ms guarantees that the catchup wait loop executes multiple times.
   245  		// This is because waitRetryTime is set to 10ms.
   246  		time.Sleep(50 * time.Millisecond)
   247  		// Do this no more than once.
   248  		vstreamHook = nil
   249  	}
   250  
   251  	filter := &binlogdatapb.Filter{
   252  		Rules: []*binlogdatapb.Rule{{
   253  			Match:  "dst",
   254  			Filter: "select * from src",
   255  		}},
   256  	}
   257  
   258  	bls := &binlogdatapb.BinlogSource{
   259  		Keyspace: env.KeyspaceName,
   260  		Shard:    env.ShardName,
   261  		Filter:   filter,
   262  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
   263  	}
   264  
   265  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
   266  	qr, err := playerEngine.Exec(query)
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	defer func() {
   271  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
   272  		if _, err := playerEngine.Exec(query); err != nil {
   273  			t.Fatal(err)
   274  		}
   275  		expectDeleteQueries(t)
   276  	}()
   277  
   278  	expectNontxQueries(t, qh.Expect(
   279  		"/insert into _vt.vreplication",
   280  		"/update _vt.vreplication set message='Picked source tablet.*",
   281  		"/insert into _vt.copy_state",
   282  		"/update _vt.vreplication set state='Copying'",
   283  		// Copy mode.
   284  		"insert into dst(idc,val) values ('a',1)",
   285  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"idc\\" type:VARCHAR} rows:{lengths:1 values:\\"a\\"}'.*`,
   286  		// Copy-catchup mode.
   287  		`/insert into dst\(idc,val\) select 'B', 3 from dual where \( .* 'B' COLLATE .* \) <= \( .* 'a' COLLATE .* \)`,
   288  	).Then(func(expect qh.ExpectationSequencer) qh.ExpectationSequencer {
   289  		// Back to copy mode.
   290  		// Inserts can happen out of order.
   291  		// Updates must happen in order.
   292  		//upd1 := expect.
   293  		upd1 := expect.Then(qh.Eventually(
   294  			"insert into dst(idc,val) values ('B',3)",
   295  			`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"idc\\" type:VARCHAR} rows:{lengths:1 values:\\"B\\"}'.*`,
   296  		))
   297  		upd2 := expect.Then(qh.Eventually(
   298  			"insert into dst(idc,val) values ('c',2)",
   299  			`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"idc\\" type:VARCHAR} rows:{lengths:1 values:\\"c\\"}'.*`,
   300  		))
   301  		upd1.Then(upd2.Eventually())
   302  		return upd2
   303  	}).Then(qh.Immediately(
   304  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst",
   305  		"/update _vt.vreplication set state='Running'",
   306  	)))
   307  
   308  	expectData(t, "dst", [][]string{
   309  		{"a", "1"},
   310  		{"B", "3"},
   311  		{"c", "2"},
   312  	})
   313  }
   314  
   315  // TestPlayerCopyVarcharPKCaseSensitiveCollation tests the copy/catchup phase for a table with varbinary columns
   316  // (which has a case sensitive collation with upper case alphabets below lower case in sort order)
   317  func TestPlayerCopyVarcharCompositePKCaseSensitiveCollation(t *testing.T) {
   318  	testVcopierTestCases(t, testPlayerCopyVarcharCompositePKCaseSensitiveCollation, commonVcopierTestCases())
   319  }
   320  
   321  func testPlayerCopyVarcharCompositePKCaseSensitiveCollation(t *testing.T) {
   322  	defer deleteTablet(addTablet(100))
   323  
   324  	reset := vstreamer.AdjustPacketSize(1)
   325  	defer reset()
   326  
   327  	savedCopyPhaseDuration := copyPhaseDuration
   328  	// copyPhaseDuration should be low enough to have time to send one row.
   329  	copyPhaseDuration = 500 * time.Millisecond
   330  	defer func() { copyPhaseDuration = savedCopyPhaseDuration }()
   331  
   332  	savedWaitRetryTime := waitRetryTime
   333  	// waitRetry time should be very low to cause the wait loop to execute multipel times.
   334  	waitRetryTime = 10 * time.Millisecond
   335  	defer func() { waitRetryTime = savedWaitRetryTime }()
   336  
   337  	execStatements(t, []string{
   338  		"create table src(id int, idc varbinary(20), idc2 varbinary(20), val int, primary key(id,idc,idc2))",
   339  		"insert into src values(1, 'a', 'a', 1), (1, 'c', 'c', 2)",
   340  		fmt.Sprintf("create table %s.dst(id int, idc varbinary(20), idc2 varbinary(20), val int, primary key(id,idc,idc2))", vrepldb),
   341  	})
   342  	defer execStatements(t, []string{
   343  		"drop table src",
   344  		fmt.Sprintf("drop table %s.dst", vrepldb),
   345  	})
   346  	env.SchemaEngine.Reload(context.Background())
   347  
   348  	count := 0
   349  	vstreamRowsSendHook = func(ctx context.Context) {
   350  		defer func() { count++ }()
   351  		// Allow the first two calls to go through: field info and one row.
   352  		if count <= 1 {
   353  			return
   354  		}
   355  		// Insert a row with PK which is < the lastPK till now because of the utf8mb4 collation
   356  		execStatements(t, []string{
   357  			"insert into src values(1, 'B', 'B', 3)",
   358  		})
   359  		// Wait for context to expire and then send the row.
   360  		// This will cause the copier to abort and go back to catchup mode.
   361  		<-ctx.Done()
   362  		// Do this no more than once.
   363  		vstreamRowsSendHook = nil
   364  	}
   365  
   366  	vstreamHook = func(context.Context) {
   367  		// Sleeping 50ms guarantees that the catchup wait loop executes multiple times.
   368  		// This is because waitRetryTime is set to 10ms.
   369  		time.Sleep(50 * time.Millisecond)
   370  		// Do this no more than once.
   371  		vstreamHook = nil
   372  	}
   373  
   374  	filter := &binlogdatapb.Filter{
   375  		Rules: []*binlogdatapb.Rule{{
   376  			Match:  "dst",
   377  			Filter: "select * from src",
   378  		}},
   379  	}
   380  
   381  	bls := &binlogdatapb.BinlogSource{
   382  		Keyspace: env.KeyspaceName,
   383  		Shard:    env.ShardName,
   384  		Filter:   filter,
   385  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
   386  	}
   387  
   388  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
   389  	qr, err := playerEngine.Exec(query)
   390  	if err != nil {
   391  		t.Fatal(err)
   392  	}
   393  	defer func() {
   394  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
   395  		if _, err := playerEngine.Exec(query); err != nil {
   396  			t.Fatal(err)
   397  		}
   398  		expectDeleteQueries(t)
   399  	}()
   400  
   401  	expectNontxQueries(t, qh.Expect(
   402  		"/insert into _vt.vreplication",
   403  		"/update _vt.vreplication set message='Picked source tablet.*",
   404  		"/insert into _vt.copy_state",
   405  		"/update _vt.vreplication set state='Copying'",
   406  		// Copy mode.
   407  		"insert into dst(id,idc,idc2,val) values (1,'a','a',1)",
   408  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} fields:{name:\\"idc\\" type:VARBINARY} fields:{name:\\"idc2\\" type:VARBINARY} rows:{lengths:1 lengths:1 lengths:1 values:\\"1aa\\"}'.*`,
   409  		// Copy-catchup mode.
   410  		`insert into dst(id,idc,idc2,val) select 1, 'B', 'B', 3 from dual where (1,'B','B') <= (1,'a','a')`,
   411  		// Copy mode.
   412  		"insert into dst(id,idc,idc2,val) values (1,'c','c',2)",
   413  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} fields:{name:\\"idc\\" type:VARBINARY} fields:{name:\\"idc2\\" type:VARBINARY} rows:{lengths:1 lengths:1 lengths:1 values:\\"1cc\\"}'.*`,
   414  		// Wrap-up.
   415  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst",
   416  		"/update _vt.vreplication set state='Running'",
   417  	))
   418  
   419  	expectData(t, "dst", [][]string{
   420  		{"1", "B", "B", "3"},
   421  		{"1", "a", "a", "1"},
   422  		{"1", "c", "c", "2"},
   423  	})
   424  }
   425  
   426  // TestPlayerCopyTablesWithFK validates that vreplication disables foreign keys during the copy phase
   427  func TestPlayerCopyTablesWithFK(t *testing.T) {
   428  	testVcopierTestCases(t, testPlayerCopyTablesWithFK, commonVcopierTestCases())
   429  }
   430  
   431  func testPlayerCopyTablesWithFK(t *testing.T) {
   432  	testForeignKeyQueries = true
   433  	defer func() {
   434  		testForeignKeyQueries = false
   435  	}()
   436  
   437  	defer deleteTablet(addTablet(100))
   438  
   439  	execStatements(t, []string{
   440  		"create table src2(id int, id2 int, primary key(id))",
   441  		"create table src1(id int, id2 int, primary key(id), foreign key (id2) references src2(id) on delete cascade)",
   442  		"insert into src2 values(1, 21), (2, 22)",
   443  		"insert into src1 values(1, 1), (2, 2)",
   444  		fmt.Sprintf("create table %s.dst2(id int, id2 int, primary key(id))", vrepldb),
   445  		fmt.Sprintf("create table %s.dst1(id int, id2 int, primary key(id), foreign key (id2) references dst2(id) on delete cascade)", vrepldb),
   446  	})
   447  	defer execStatements(t, []string{
   448  		"drop table src1",
   449  		fmt.Sprintf("drop table %s.dst1", vrepldb),
   450  		"drop table src2",
   451  		fmt.Sprintf("drop table %s.dst2", vrepldb),
   452  	})
   453  	env.SchemaEngine.Reload(context.Background())
   454  
   455  	filter := &binlogdatapb.Filter{
   456  		Rules: []*binlogdatapb.Rule{{
   457  			Match:  "dst1",
   458  			Filter: "select * from src1",
   459  		}, {
   460  			Match:  "dst2",
   461  			Filter: "select * from src2",
   462  		}},
   463  	}
   464  
   465  	bls := &binlogdatapb.BinlogSource{
   466  		Keyspace: env.KeyspaceName,
   467  		Shard:    env.ShardName,
   468  		Filter:   filter,
   469  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
   470  	}
   471  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
   472  	qr, err := playerEngine.Exec(query)
   473  	require.NoError(t, err)
   474  
   475  	expectDBClientQueries(t, qh.Expect(
   476  		"/insert into _vt.vreplication",
   477  		"/update _vt.vreplication set message='Picked source tablet.*",
   478  		"select @@foreign_key_checks;",
   479  		// Create the list of tables to copy and transition to Copying state.
   480  		"begin",
   481  		"/insert into _vt.copy_state",
   482  		"/update _vt.vreplication set state='Copying'",
   483  		"commit",
   484  		"set foreign_key_checks=0;",
   485  		// The first fast-forward has no starting point. So, it just saves the current position.
   486  		"/update _vt.vreplication set pos=",
   487  	).Then(func(expect qh.ExpectationSequencer) qh.ExpectationSequencer {
   488  		// With parallel inserts, new db client connects are created on-the-fly.
   489  		if vreplicationParallelInsertWorkers > 1 {
   490  			return expect.Then(qh.Eventually("set foreign_key_checks=0;"))
   491  		}
   492  		return expect
   493  	}).Then(qh.Eventually(
   494  		// Copy.
   495  		// Inserts may happen out-of-order. Update happen in-order.
   496  		"begin",
   497  		"insert into dst1(id,id2) values (1,1), (2,2)",
   498  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"2\\"}'.*`,
   499  		"commit",
   500  	)).Then(qh.Immediately(
   501  		"set foreign_key_checks=0;",
   502  		// copy of dst1 is done: delete from copy_state.
   503  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst1",
   504  		// The next FF executes and updates the position before copying.
   505  		"set foreign_key_checks=0;",
   506  		"begin",
   507  		"/update _vt.vreplication set pos=",
   508  		"commit",
   509  	)).Then(func(expect qh.ExpectationSequencer) qh.ExpectationSequencer {
   510  		// With parallel inserts, new db client connects are created on-the-fly.
   511  		if vreplicationParallelInsertWorkers > 1 {
   512  			return expect.Then(qh.Eventually("set foreign_key_checks=0;"))
   513  		}
   514  		return expect
   515  	}).Then(qh.Eventually(
   516  		// copy dst2
   517  		"begin",
   518  		"insert into dst2(id,id2) values (1,21), (2,22)",
   519  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"2\\"}'.*`,
   520  		"commit",
   521  	)).Then(qh.Immediately(
   522  		"set foreign_key_checks=0;",
   523  		// copy of dst1 is done: delete from copy_state.
   524  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst2",
   525  		// All tables copied. Final catch up followed by Running state.
   526  		"set foreign_key_checks=1;",
   527  		"/update _vt.vreplication set state='Running'",
   528  	)))
   529  
   530  	expectData(t, "dst1", [][]string{
   531  		{"1", "1"},
   532  		{"2", "2"},
   533  	})
   534  	expectData(t, "dst2", [][]string{
   535  		{"1", "21"},
   536  		{"2", "22"},
   537  	})
   538  
   539  	validateCopyRowCountStat(t, 4)
   540  
   541  	query = fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
   542  	if _, err := playerEngine.Exec(query); err != nil {
   543  		t.Fatal(err)
   544  	}
   545  	expectDBClientQueries(t, qh.Expect(
   546  		"set foreign_key_checks=1;",
   547  		"begin",
   548  		"/delete from _vt.vreplication",
   549  		"/delete from _vt.copy_state",
   550  		"/delete from _vt.post_copy_action",
   551  		"commit",
   552  	))
   553  }
   554  
   555  func TestPlayerCopyTables(t *testing.T) {
   556  	testVcopierTestCases(t, testPlayerCopyTables, commonVcopierTestCases())
   557  }
   558  
   559  func testPlayerCopyTables(t *testing.T) {
   560  	defer deleteTablet(addTablet(100))
   561  
   562  	execStatements(t, []string{
   563  		"create table src1(id int, val varbinary(128), d decimal(8,0), primary key(id))",
   564  		"insert into src1 values(2, 'bbb', 1), (1, 'aaa', 0)",
   565  		fmt.Sprintf("create table %s.dst1(id int, val varbinary(128), val2 varbinary(128), d decimal(8,0), primary key(id))", vrepldb),
   566  		"create table yes(id int, val varbinary(128), primary key(id))",
   567  		fmt.Sprintf("create table %s.yes(id int, val varbinary(128), primary key(id))", vrepldb),
   568  		"create table no(id int, val varbinary(128), primary key(id))",
   569  	})
   570  	defer execStatements(t, []string{
   571  		"drop table src1",
   572  		fmt.Sprintf("drop table %s.dst1", vrepldb),
   573  		"drop table yes",
   574  		fmt.Sprintf("drop table %s.yes", vrepldb),
   575  		"drop table no",
   576  	})
   577  	env.SchemaEngine.Reload(context.Background())
   578  
   579  	filter := &binlogdatapb.Filter{
   580  		Rules: []*binlogdatapb.Rule{{
   581  			Match:  "dst1",
   582  			Filter: "select id, val, val as val2, d from src1",
   583  		}, {
   584  			Match: "/yes",
   585  		}},
   586  	}
   587  
   588  	bls := &binlogdatapb.BinlogSource{
   589  		Keyspace: env.KeyspaceName,
   590  		Shard:    env.ShardName,
   591  		Filter:   filter,
   592  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
   593  	}
   594  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
   595  	qr, err := playerEngine.Exec(query)
   596  	if err != nil {
   597  		t.Fatal(err)
   598  	}
   599  	defer func() {
   600  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
   601  		if _, err := playerEngine.Exec(query); err != nil {
   602  			t.Fatal(err)
   603  		}
   604  		expectDeleteQueries(t)
   605  	}()
   606  
   607  	expectDBClientQueries(t, qh.Expect(
   608  		"/insert into _vt.vreplication",
   609  		"/update _vt.vreplication set message='Picked source tablet.*",
   610  		// Create the list of tables to copy and transition to Copying state.
   611  		"begin",
   612  		"/insert into _vt.copy_state",
   613  		"/update _vt.vreplication set state='Copying'",
   614  		"commit",
   615  		// The first fast-forward has no starting point. So, it just saves the current position.
   616  		"/update _vt.vreplication set pos=",
   617  		"begin",
   618  		"insert into dst1(id,val,val2,d) values (1,'aaa','aaa',0), (2,'bbb','bbb',1)",
   619  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"2\\"}'.*`,
   620  		"commit",
   621  		// copy of dst1 is done: delete from copy_state.
   622  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst1",
   623  		// The next FF executes and updates the position before copying.
   624  		"begin",
   625  		"/update _vt.vreplication set pos=",
   626  		"commit",
   627  		// Nothing to copy from yes. Delete from copy_state.
   628  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*yes",
   629  		// All tables copied. Final catch up followed by Running state.
   630  		"/update _vt.vreplication set state='Running'",
   631  	))
   632  	expectData(t, "dst1", [][]string{
   633  		{"1", "aaa", "aaa", "0"},
   634  		{"2", "bbb", "bbb", "1"},
   635  	})
   636  	expectData(t, "yes", [][]string{})
   637  	validateCopyRowCountStat(t, 2)
   638  	ctx, cancel := context.WithCancel(context.Background())
   639  
   640  	type logTestCase struct {
   641  		name string
   642  		typ  string
   643  	}
   644  	testCases := []logTestCase{
   645  		{name: "Check log for start of copy", typ: "LogCopyStarted"},
   646  		{name: "Check log for end of copy", typ: "LogCopyEnded"},
   647  	}
   648  	for _, testCase := range testCases {
   649  		t.Run(testCase.name, func(t *testing.T) {
   650  			query = fmt.Sprintf("select count(*) from _vt.vreplication_log where type = '%s'", testCase.typ)
   651  			qr, err := env.Mysqld.FetchSuperQuery(ctx, query)
   652  			require.NoError(t, err)
   653  			require.NotNil(t, qr)
   654  			require.Equal(t, 1, len(qr.Rows))
   655  		})
   656  	}
   657  	cancel()
   658  
   659  }
   660  
   661  // TestPlayerCopyBigTable ensures the copy-catchup back-and-forth loop works correctly.
   662  func TestPlayerCopyBigTable(t *testing.T) {
   663  	testVcopierTestCases(t, testPlayerCopyBigTable, commonVcopierTestCases())
   664  }
   665  
   666  func testPlayerCopyBigTable(t *testing.T) {
   667  	defer deleteTablet(addTablet(100))
   668  
   669  	reset := vstreamer.AdjustPacketSize(1)
   670  	defer reset()
   671  
   672  	savedCopyPhaseDuration := copyPhaseDuration
   673  	// copyPhaseDuration should be low enough to have time to send one row.
   674  	copyPhaseDuration = 500 * time.Millisecond
   675  	defer func() { copyPhaseDuration = savedCopyPhaseDuration }()
   676  
   677  	savedWaitRetryTime := waitRetryTime
   678  	// waitRetry time should be very low to cause the wait loop to execute multiple times.
   679  	waitRetryTime = 10 * time.Millisecond
   680  	defer func() { waitRetryTime = savedWaitRetryTime }()
   681  
   682  	execStatements(t, []string{
   683  		"create table src(id int, val varbinary(128), primary key(id))",
   684  		"insert into src values(1, 'aaa'), (2, 'bbb')",
   685  		fmt.Sprintf("create table %s.dst(id int, val varbinary(128), primary key(id))", vrepldb),
   686  	})
   687  	defer execStatements(t, []string{
   688  		"drop table src",
   689  		fmt.Sprintf("drop table %s.dst", vrepldb),
   690  	})
   691  	env.SchemaEngine.Reload(context.Background())
   692  
   693  	count := 0
   694  	vstreamRowsSendHook = func(ctx context.Context) {
   695  		defer func() { count++ }()
   696  		// Allow the first two calls to go through: field info and one row.
   697  		if count <= 1 {
   698  			return
   699  		}
   700  		// Insert a statement to test that catchup gets new events.
   701  		execStatements(t, []string{
   702  			"insert into src values(3, 'ccc')",
   703  		})
   704  		// Wait for context to expire and then send the row.
   705  		// This will cause the copier to abort and go back to catchup mode.
   706  		<-ctx.Done()
   707  		// Do this at most once.
   708  		vstreamRowsSendHook = nil
   709  	}
   710  
   711  	vstreamHook = func(context.Context) {
   712  		// Sleeping 50ms guarantees that the catchup wait loop executes multiple times.
   713  		// This is because waitRetryTime is set to 10ms.
   714  		time.Sleep(50 * time.Millisecond)
   715  		// Do this no more than once.
   716  		vstreamHook = nil
   717  	}
   718  
   719  	filter := &binlogdatapb.Filter{
   720  		Rules: []*binlogdatapb.Rule{{
   721  			Match:  "dst",
   722  			Filter: "select * from src",
   723  		}},
   724  	}
   725  
   726  	bls := &binlogdatapb.BinlogSource{
   727  		Keyspace: env.KeyspaceName,
   728  		Shard:    env.ShardName,
   729  		Filter:   filter,
   730  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
   731  	}
   732  
   733  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
   734  	qr, err := playerEngine.Exec(query)
   735  	if err != nil {
   736  		t.Fatal(err)
   737  	}
   738  	defer func() {
   739  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
   740  		if _, err := playerEngine.Exec(query); err != nil {
   741  			t.Fatal(err)
   742  		}
   743  		expectDeleteQueries(t)
   744  	}()
   745  
   746  	expectNontxQueries(t, qh.Expect(
   747  		"/insert into _vt.vreplication",
   748  		"/update _vt.vreplication set message='Picked source tablet.*",
   749  		"/insert into _vt.copy_state",
   750  		// The first fast-forward has no starting point. So, it just saves the current position.
   751  		"/update _vt.vreplication set state='Copying'",
   752  		"insert into dst(id,val) values (1,'aaa')",
   753  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"1\\"}'.*`,
   754  		// The next catchup executes the new row insert, but will be a no-op.
   755  		"insert into dst(id,val) select 3, 'ccc' from dual where (3) <= (1)",
   756  		// fastForward has nothing to add. Just saves position.
   757  		// Back to copy mode.
   758  		// Inserts can happen out-of-order.
   759  		// Updates happen in-order.
   760  	).Then(func(expect qh.ExpectationSequencer) qh.ExpectationSequencer {
   761  		ins1 := expect.Then(qh.Eventually("insert into dst(id,val) values (2,'bbb')"))
   762  		upd1 := ins1.Then(qh.Eventually(
   763  			`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"2\\"}'.*`,
   764  		))
   765  		// Third row copied without going back to catchup state.
   766  		ins3 := expect.Then(qh.Eventually("insert into dst(id,val) values (3,'ccc')"))
   767  		upd3 := ins3.Then(qh.Eventually(
   768  			`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"3\\"}'.*`,
   769  		))
   770  		upd1.Then(upd3.Eventually())
   771  		return upd3
   772  	}).Then(qh.Eventually(
   773  		// Wrap-up.
   774  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst",
   775  		// Copy is done. Go into running state.
   776  		// All tables copied. Final catch up followed by Running state.
   777  		"/update _vt.vreplication set state='Running'",
   778  	)))
   779  
   780  	expectData(t, "dst", [][]string{
   781  		{"1", "aaa"},
   782  		{"2", "bbb"},
   783  		{"3", "ccc"},
   784  	})
   785  	validateCopyRowCountStat(t, 3)
   786  
   787  	// this check is very flaky in CI and should be done manually while testing catchup locally
   788  	// validateQueryCountStat(t, "catchup", 1)
   789  }
   790  
   791  // TestPlayerCopyWildcardRule ensures the copy-catchup back-and-forth loop works correctly
   792  // when the filter uses a wildcard rule
   793  func TestPlayerCopyWildcardRule(t *testing.T) {
   794  	testVcopierTestCases(t, testPlayerCopyWildcardRule, commonVcopierTestCases())
   795  }
   796  
   797  func testPlayerCopyWildcardRule(t *testing.T) {
   798  	defer deleteTablet(addTablet(100))
   799  
   800  	reset := vstreamer.AdjustPacketSize(1)
   801  	defer reset()
   802  
   803  	savedCopyPhaseDuration := copyPhaseDuration
   804  	// copyPhaseDuration should be low enough to have time to send one row.
   805  	copyPhaseDuration = 500 * time.Millisecond
   806  	defer func() { copyPhaseDuration = savedCopyPhaseDuration }()
   807  
   808  	savedWaitRetryTime := waitRetryTime
   809  	// waitRetry time should be very low to cause the wait loop to execute multipel times.
   810  	waitRetryTime = 10 * time.Millisecond
   811  	defer func() { waitRetryTime = savedWaitRetryTime }()
   812  
   813  	execStatements(t, []string{
   814  		"create table src(id int, val varbinary(128), primary key(id))",
   815  		"insert into src values(1, 'aaa'), (2, 'bbb')",
   816  		fmt.Sprintf("create table %s.src(id int, val varbinary(128), primary key(id))", vrepldb),
   817  	})
   818  	defer execStatements(t, []string{
   819  		"drop table src",
   820  		fmt.Sprintf("drop table %s.src", vrepldb),
   821  	})
   822  	env.SchemaEngine.Reload(context.Background())
   823  
   824  	count := 0
   825  	vstreamRowsSendHook = func(ctx context.Context) {
   826  		defer func() { count++ }()
   827  		// Allow the first two calls to go through: field info and one row.
   828  		if count <= 1 {
   829  			return
   830  		}
   831  		// Insert a statement to test that catchup gets new events.
   832  		execStatements(t, []string{
   833  			"insert into src values(3, 'ccc')",
   834  		})
   835  		// Wait for context to expire and then send the row.
   836  		// This will cause the copier to abort and go back to catchup mode.
   837  		<-ctx.Done()
   838  		// Do this no more than once.
   839  		vstreamRowsSendHook = nil
   840  	}
   841  
   842  	vstreamHook = func(context.Context) {
   843  		// Sleeping 50ms guarantees that the catchup wait loop executes multiple times.
   844  		// This is because waitRetryTime is set to 10ms.
   845  		time.Sleep(50 * time.Millisecond)
   846  		// Do this no more than once.
   847  		vstreamHook = nil
   848  	}
   849  
   850  	filter := &binlogdatapb.Filter{
   851  		Rules: []*binlogdatapb.Rule{{
   852  			Match:  "/.*",
   853  			Filter: "",
   854  		}},
   855  	}
   856  
   857  	bls := &binlogdatapb.BinlogSource{
   858  		Keyspace: env.KeyspaceName,
   859  		Shard:    env.ShardName,
   860  		Filter:   filter,
   861  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
   862  	}
   863  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
   864  	qr, err := playerEngine.Exec(query)
   865  	if err != nil {
   866  		t.Fatal(err)
   867  	}
   868  	defer func() {
   869  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
   870  		if _, err := playerEngine.Exec(query); err != nil {
   871  			t.Fatal(err)
   872  		}
   873  		expectDeleteQueries(t)
   874  	}()
   875  
   876  	expectNontxQueries(t, qh.Expect(
   877  		"/insert into _vt.vreplication",
   878  		"/update _vt.vreplication set message='Picked source tablet.*",
   879  		"/insert into _vt.copy_state",
   880  		"/update _vt.vreplication set state='Copying'",
   881  		// The first fast-forward has no starting point. So, it just saves the current position.
   882  		"insert into src(id,val) values (1,'aaa')",
   883  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"1\\"}'.*`,
   884  		// The next catchup executes the new row insert, but will be a no-op.
   885  		"insert into src(id,val) select 3, 'ccc' from dual where (3) <= (1)",
   886  		// fastForward has nothing to add. Just saves position.
   887  		// Return to copy mode.
   888  		// Inserts can happen out-of-order.
   889  		// Updates happen in-order.
   890  	).Then(func(expect qh.ExpectationSequencer) qh.ExpectationSequencer {
   891  		ins1 := expect.Then(qh.Eventually("insert into src(id,val) values (2,'bbb')"))
   892  		upd1 := ins1.Then(qh.Eventually(
   893  			`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"2\\"}'.*`,
   894  		))
   895  		// Third row copied without going back to catchup state.
   896  		ins3 := expect.Then(qh.Eventually("insert into src(id,val) values (3,'ccc')"))
   897  		upd3 := ins3.Then(qh.Eventually(
   898  			`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"3\\"}'.*`,
   899  		))
   900  		upd1.Then(upd3.Eventually())
   901  		return upd3
   902  	}).Then(qh.Immediately(
   903  		// Wrap-up.
   904  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*src",
   905  		// Copy is done. Go into running state.
   906  		"/update _vt.vreplication set state='Running'",
   907  	)))
   908  
   909  	expectData(t, "src", [][]string{
   910  		{"1", "aaa"},
   911  		{"2", "bbb"},
   912  		{"3", "ccc"},
   913  	})
   914  }
   915  
   916  // TestPlayerCopyTableContinuation tests the copy workflow where tables have been partially copied.
   917  // TODO(maxenglander): this test isn't repeatable, even with the same flags.
   918  func TestPlayerCopyTableContinuation(t *testing.T) {
   919  	testVcopierTestCases(t, testPlayerCopyTableContinuation, []vcopierTestCase{
   920  		{
   921  			vreplicationExperimentalFlags: 0,
   922  		},
   923  	})
   924  }
   925  
   926  func testPlayerCopyTableContinuation(t *testing.T) {
   927  	defer deleteTablet(addTablet(100))
   928  
   929  	execStatements(t, []string{
   930  		// src1 is initialized as partially copied.
   931  		// lastpk will be initialized at (6,6) later below.
   932  		// dst1 only copies id1 and val. This will allow us to test for correctness if id2 changes in the source.
   933  		"create table src1(id1 int, id2 int, val varbinary(128), primary key(id1, id2))",
   934  		"insert into src1 values(2,2,'no change'), (3,3,'update'), (4,4,'delete'), (5,5,'move within'), (6,6,'move out'), (8,8,'no change'), (9,9,'delete'), (10,10,'update'), (11,11,'move in')",
   935  		fmt.Sprintf("create table %s.dst1(id int, val varbinary(128), primary key(id))", vrepldb),
   936  		fmt.Sprintf("insert into %s.dst1 values(2,'no change'), (3,'update'), (4,'delete'), (5,'move within'), (6,'move out')", vrepldb),
   937  		// copied is initialized as fully copied
   938  		"create table copied(id int, val varbinary(128), primary key(id))",
   939  		"insert into copied values(1,'aaa')",
   940  		fmt.Sprintf("create table %s.copied(id int, val varbinary(128), primary key(id))", vrepldb),
   941  		fmt.Sprintf("insert into %s.copied values(1,'aaa')", vrepldb),
   942  		// not_copied yet to be copied.
   943  		"create table not_copied(id int, val varbinary(128), primary key(id))",
   944  		"insert into not_copied values(1,'aaa')",
   945  		fmt.Sprintf("create table %s.not_copied(id int, val varbinary(128), primary key(id))", vrepldb),
   946  	})
   947  	defer execStatements(t, []string{
   948  		"drop table src1",
   949  		fmt.Sprintf("drop table %s.dst1", vrepldb),
   950  	})
   951  	env.SchemaEngine.Reload(context.Background())
   952  
   953  	filter := &binlogdatapb.Filter{
   954  		Rules: []*binlogdatapb.Rule{{
   955  			Match:  "dst1",
   956  			Filter: "select id1 as id, val from src1",
   957  		}, {
   958  			Match:  "copied",
   959  			Filter: "select * from copied",
   960  		}, {
   961  			Match:  "not_copied",
   962  			Filter: "select * from not_copied",
   963  		}},
   964  	}
   965  	pos := primaryPosition(t)
   966  	execStatements(t, []string{
   967  		// insert inside and outside current range.
   968  		"insert into src1 values(1,1,'insert in'), (7,7,'insert out')",
   969  		// update inside and outside current range.
   970  		"update src1 set val='updated' where id1 in (3,10)",
   971  		// delete inside and outside current range.
   972  		"delete from src1 where id1 in (4,9)",
   973  		// move row within range by changing id2.
   974  		"update src1 set id2=10 where id1=5",
   975  		// move row from within to outside range.
   976  		"update src1 set id1=12 where id1=6",
   977  		// move row from outside to witihn range.
   978  		"update src1 set id1=4 where id1=11",
   979  		// modify the copied table.
   980  		"update copied set val='bbb' where id=1",
   981  		// modify the uncopied table.
   982  		"update not_copied set val='bbb' where id=1",
   983  	})
   984  
   985  	// Set a hook to execute statements just before the copy begins from src1.
   986  	vstreamRowsHook = func(context.Context) {
   987  		execStatements(t, []string{
   988  			"update src1 set val='updated again' where id1 = 3",
   989  		})
   990  		// Set it back to nil. Otherwise, this will get executed again when copying not_copied.
   991  		vstreamRowsHook = nil
   992  	}
   993  
   994  	bls := &binlogdatapb.BinlogSource{
   995  		Keyspace: env.KeyspaceName,
   996  		Shard:    env.ShardName,
   997  		Filter:   filter,
   998  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
   999  	}
  1000  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.BlpStopped, playerEngine.dbName, 0, 0)
  1001  	qr, err := playerEngine.Exec(query)
  1002  	if err != nil {
  1003  		t.Fatal(err)
  1004  	}
  1005  	// As mentioned above. lastpk cut-off is set at (6,6)
  1006  	lastpk := sqltypes.ResultToProto3(sqltypes.MakeTestResult(
  1007  		sqltypes.MakeTestFields(
  1008  			"id1|id2",
  1009  			"int32|int32",
  1010  		),
  1011  		"6|6",
  1012  	))
  1013  	lastpk.RowsAffected = 0
  1014  	execStatements(t, []string{
  1015  		fmt.Sprintf("insert into _vt.copy_state (vrepl_id, table_name, lastpk) values(%d, '%s', %s)", qr.InsertID, "dst1", encodeString(fmt.Sprintf("%v", lastpk))),
  1016  		fmt.Sprintf("insert into _vt.copy_state (vrepl_id, table_name, lastpk) values(%d, '%s', null)", qr.InsertID, "not_copied"),
  1017  	})
  1018  	id := qr.InsertID
  1019  	_, err = playerEngine.Exec(fmt.Sprintf("update _vt.vreplication set state='Copying', pos=%s where id=%d", encodeString(pos), id))
  1020  	if err != nil {
  1021  		t.Fatal(err)
  1022  	}
  1023  	defer func() {
  1024  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", id)
  1025  		if _, err := playerEngine.Exec(query); err != nil {
  1026  			t.Fatal(err)
  1027  		}
  1028  		expectDeleteQueries(t)
  1029  	}()
  1030  
  1031  	for q := range globalDBQueries {
  1032  		if strings.HasPrefix(q, "update") {
  1033  			break
  1034  		}
  1035  	}
  1036  
  1037  	expectNontxQueries(t, qh.Expect(
  1038  		// Catchup
  1039  		"/update _vt.vreplication set message='Picked source tablet.*",
  1040  		"insert into dst1(id,val) select 1, 'insert in' from dual where (1,1) <= (6,6)",
  1041  		"insert into dst1(id,val) select 7, 'insert out' from dual where (7,7) <= (6,6)",
  1042  		"update dst1 set val='updated' where id=3 and (3,3) <= (6,6)",
  1043  		"update dst1 set val='updated' where id=10 and (10,10) <= (6,6)",
  1044  		"delete from dst1 where id=4 and (4,4) <= (6,6)",
  1045  		"delete from dst1 where id=9 and (9,9) <= (6,6)",
  1046  		"delete from dst1 where id=5 and (5,5) <= (6,6)",
  1047  		"insert into dst1(id,val) select 5, 'move within' from dual where (5,10) <= (6,6)",
  1048  		"delete from dst1 where id=6 and (6,6) <= (6,6)",
  1049  		"insert into dst1(id,val) select 12, 'move out' from dual where (12,6) <= (6,6)",
  1050  		"delete from dst1 where id=11 and (11,11) <= (6,6)",
  1051  		"insert into dst1(id,val) select 4, 'move in' from dual where (4,11) <= (6,6)",
  1052  		"update copied set val='bbb' where id=1",
  1053  		// Fast-forward
  1054  		"update dst1 set val='updated again' where id=3 and (3,3) <= (6,6)",
  1055  	).Then(qh.Immediately(
  1056  		"insert into dst1(id,val) values (7,'insert out'), (8,'no change'), (10,'updated'), (12,'move out')",
  1057  	)).Then(qh.Eventually(
  1058  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id1\\" type:INT32} fields:{name:\\"id2\\" type:INT32} rows:{lengths:2 lengths:1 values:\\"126\\"}'.*`,
  1059  	)).Then(qh.Immediately(
  1060  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst1",
  1061  		"insert into not_copied(id,val) values (1,'bbb')",
  1062  	)).Then(qh.Eventually(
  1063  		// Copy again. There should be no events for catchup.
  1064  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\\"id\\\" type:INT32} rows:{lengths:1 values:\\\"1\\\"}'.*`,
  1065  	)).Then(qh.Immediately(
  1066  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*not_copied",
  1067  		"/update _vt.vreplication set state='Running'",
  1068  	)))
  1069  
  1070  	expectData(t, "dst1", [][]string{
  1071  		{"1", "insert in"},
  1072  		{"2", "no change"},
  1073  		{"3", "updated again"},
  1074  		{"4", "move in"},
  1075  		{"5", "move within"},
  1076  		{"7", "insert out"},
  1077  		{"8", "no change"},
  1078  		{"10", "updated"},
  1079  		{"12", "move out"},
  1080  	})
  1081  	expectData(t, "copied", [][]string{
  1082  		{"1", "bbb"},
  1083  	})
  1084  	expectData(t, "not_copied", [][]string{
  1085  		{"1", "bbb"},
  1086  	})
  1087  }
  1088  
  1089  // TestPlayerCopyWildcardTableContinuation.
  1090  func TestPlayerCopyWildcardTableContinuation(t *testing.T) {
  1091  	testVcopierTestCases(t, testPlayerCopyWildcardTableContinuation, commonVcopierTestCases())
  1092  	testVcopierTestCases(t, testPlayerCopyWildcardTableContinuation, []vcopierTestCase{
  1093  		// Optimize inserts without parallel inserts.
  1094  		{
  1095  			vreplicationExperimentalFlags: vreplicationExperimentalFlagOptimizeInserts,
  1096  		},
  1097  		// Optimize inserts with parallel inserts.
  1098  		{
  1099  			vreplicationExperimentalFlags:     vreplicationExperimentalFlagOptimizeInserts,
  1100  			vreplicationParallelInsertWorkers: 4,
  1101  		},
  1102  	})
  1103  }
  1104  
  1105  func testPlayerCopyWildcardTableContinuation(t *testing.T) {
  1106  	defer deleteTablet(addTablet(100))
  1107  
  1108  	execStatements(t, []string{
  1109  		"create table src(id int, val varbinary(128), primary key(id))",
  1110  		"insert into src values(2,'copied'), (3,'uncopied')",
  1111  		fmt.Sprintf("create table %s.dst(id int, val varbinary(128), primary key(id))", vrepldb),
  1112  		fmt.Sprintf("insert into %s.dst values(2,'copied')", vrepldb),
  1113  	})
  1114  	defer execStatements(t, []string{
  1115  		"drop table src",
  1116  		fmt.Sprintf("drop table %s.dst", vrepldb),
  1117  	})
  1118  	env.SchemaEngine.Reload(context.Background())
  1119  
  1120  	filter := &binlogdatapb.Filter{
  1121  		Rules: []*binlogdatapb.Rule{{
  1122  			Match:  "dst",
  1123  			Filter: "select * from src",
  1124  		}},
  1125  	}
  1126  	pos := primaryPosition(t)
  1127  	execStatements(t, []string{
  1128  		"insert into src values(4,'new')",
  1129  	})
  1130  
  1131  	bls := &binlogdatapb.BinlogSource{
  1132  		Keyspace: env.KeyspaceName,
  1133  		Shard:    env.ShardName,
  1134  		Filter:   filter,
  1135  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
  1136  	}
  1137  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.BlpStopped, playerEngine.dbName, 0, 0)
  1138  	qr, err := playerEngine.Exec(query)
  1139  	if err != nil {
  1140  		t.Fatal(err)
  1141  	}
  1142  	lastpk := sqltypes.ResultToProto3(sqltypes.MakeTestResult(sqltypes.MakeTestFields(
  1143  		"id",
  1144  		"int32"),
  1145  		"2",
  1146  	))
  1147  	lastpk.RowsAffected = 0
  1148  	execStatements(t, []string{
  1149  		fmt.Sprintf("insert into _vt.copy_state (vrepl_id, table_name, lastpk) values(%d, '%s', %s)", qr.InsertID, "dst", encodeString(fmt.Sprintf("%v", lastpk))),
  1150  	})
  1151  	id := qr.InsertID
  1152  	_, err = playerEngine.Exec(fmt.Sprintf("update _vt.vreplication set state='Copying', pos=%s where id=%d", encodeString(pos), id))
  1153  	if err != nil {
  1154  		t.Fatal(err)
  1155  	}
  1156  	defer func() {
  1157  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", id)
  1158  		if _, err := playerEngine.Exec(query); err != nil {
  1159  			t.Fatal(err)
  1160  		}
  1161  		expectDeleteQueries(t)
  1162  	}()
  1163  
  1164  	optimizeInsertsEnabled := vreplicationExperimentalFlags /**/ & /**/ vreplicationExperimentalFlagOptimizeInserts != 0
  1165  
  1166  	expectNontxQueries(t, qh.Expect(
  1167  		"/insert into _vt.vreplication",
  1168  		"/update _vt.vreplication set state = 'Copying'",
  1169  		"/update _vt.vreplication set message='Picked source tablet.*",
  1170  	).Then(func(expect qh.ExpectationSequencer) qh.ExpectationSequencer {
  1171  		if !optimizeInsertsEnabled {
  1172  			expect = expect.Then(qh.Immediately("insert into dst(id,val) select 4, 'new' from dual where (4) <= (2)"))
  1173  		}
  1174  		return expect.Then(qh.Immediately("insert into dst(id,val) values (3,'uncopied'), (4,'new')"))
  1175  	}).Then(qh.Immediately(
  1176  		`/insert into _vt.copy_state .*`,
  1177  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst",
  1178  		"/update _vt.vreplication set state='Running'",
  1179  	)))
  1180  
  1181  	expectData(t, "dst", [][]string{
  1182  		{"2", "copied"},
  1183  		{"3", "uncopied"},
  1184  		{"4", "new"},
  1185  	})
  1186  	if optimizeInsertsEnabled {
  1187  		for _, ct := range playerEngine.controllers {
  1188  			require.Equal(t, int64(1), ct.blpStats.NoopQueryCount.Counts()["insert"])
  1189  			break
  1190  		}
  1191  	}
  1192  }
  1193  
  1194  // TestPlayerCopyWildcardTableContinuationWithOptimizeInserts tests the copy workflow where tables have been partially copied
  1195  // enabling the optimize inserts functionality
  1196  func TestPlayerCopyWildcardTableContinuationWithOptimizeInserts(t *testing.T) {
  1197  	oldVreplicationExperimentalFlags := vreplicationExperimentalFlags
  1198  	vreplicationExperimentalFlags = vreplicationExperimentalFlagOptimizeInserts
  1199  	defer func() {
  1200  		vreplicationExperimentalFlags = oldVreplicationExperimentalFlags
  1201  	}()
  1202  
  1203  	defer deleteTablet(addTablet(100))
  1204  
  1205  	execStatements(t, []string{
  1206  		"create table src(id int, val varbinary(128), primary key(id))",
  1207  		"insert into src values(2,'copied'), (3,'uncopied')",
  1208  		fmt.Sprintf("create table %s.dst(id int, val varbinary(128), primary key(id))", vrepldb),
  1209  		fmt.Sprintf("insert into %s.dst values(2,'copied')", vrepldb),
  1210  	})
  1211  	defer execStatements(t, []string{
  1212  		"drop table src",
  1213  		fmt.Sprintf("drop table %s.dst", vrepldb),
  1214  	})
  1215  	env.SchemaEngine.Reload(context.Background())
  1216  
  1217  	filter := &binlogdatapb.Filter{
  1218  		Rules: []*binlogdatapb.Rule{{
  1219  			Match:  "dst",
  1220  			Filter: "select * from src",
  1221  		}},
  1222  	}
  1223  	pos := primaryPosition(t)
  1224  	execStatements(t, []string{
  1225  		"insert into src values(4,'new')",
  1226  	})
  1227  
  1228  	bls := &binlogdatapb.BinlogSource{
  1229  		Keyspace: env.KeyspaceName,
  1230  		Shard:    env.ShardName,
  1231  		Filter:   filter,
  1232  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
  1233  	}
  1234  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.BlpStopped, playerEngine.dbName, 0, 0)
  1235  	qr, err := playerEngine.Exec(query)
  1236  	if err != nil {
  1237  		t.Fatal(err)
  1238  	}
  1239  	lastpk := sqltypes.ResultToProto3(sqltypes.MakeTestResult(sqltypes.MakeTestFields(
  1240  		"id",
  1241  		"int32"),
  1242  		"2",
  1243  	))
  1244  	lastpk.RowsAffected = 0
  1245  	execStatements(t, []string{
  1246  		fmt.Sprintf("insert into _vt.copy_state (vrepl_id, table_name, lastpk) values(%d, '%s', %s)", qr.InsertID, "dst", encodeString(fmt.Sprintf("%v", lastpk))),
  1247  	})
  1248  	id := qr.InsertID
  1249  	_, err = playerEngine.Exec(fmt.Sprintf("update _vt.vreplication set state='Copying', pos=%s where id=%d", encodeString(pos), id))
  1250  	if err != nil {
  1251  		t.Fatal(err)
  1252  	}
  1253  	defer func() {
  1254  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", id)
  1255  		if _, err := playerEngine.Exec(query); err != nil {
  1256  			t.Fatal(err)
  1257  		}
  1258  		expectDeleteQueries(t)
  1259  	}()
  1260  
  1261  	expectNontxQueries(t, qh.Expect(
  1262  		// Catchup
  1263  		"/insert into _vt.vreplication",
  1264  		"/update _vt.vreplication set state = 'Copying'",
  1265  		"/update _vt.vreplication set message='Picked source tablet.*",
  1266  		// Copy
  1267  		"insert into dst(id,val) values (3,'uncopied'), (4,'new')",
  1268  		`/insert into _vt.copy_state .*`,
  1269  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst",
  1270  		"/update _vt.vreplication set state='Running'",
  1271  	))
  1272  	expectData(t, "dst", [][]string{
  1273  		{"2", "copied"},
  1274  		{"3", "uncopied"},
  1275  		{"4", "new"},
  1276  	})
  1277  	for _, ct := range playerEngine.controllers {
  1278  		require.Equal(t, int64(1), ct.blpStats.NoopQueryCount.Counts()["insert"])
  1279  		break
  1280  	}
  1281  }
  1282  
  1283  func TestPlayerCopyTablesNone(t *testing.T) {
  1284  	testVcopierTestCases(t, testPlayerCopyTablesNone, commonVcopierTestCases())
  1285  }
  1286  
  1287  func testPlayerCopyTablesNone(t *testing.T) {
  1288  	defer deleteTablet(addTablet(100))
  1289  
  1290  	filter := &binlogdatapb.Filter{
  1291  		Rules: []*binlogdatapb.Rule{{
  1292  			Match:  "dst1",
  1293  			Filter: "select * from src1",
  1294  		}},
  1295  	}
  1296  
  1297  	bls := &binlogdatapb.BinlogSource{
  1298  		Keyspace: env.KeyspaceName,
  1299  		Shard:    env.ShardName,
  1300  		Filter:   filter,
  1301  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
  1302  	}
  1303  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
  1304  	qr, err := playerEngine.Exec(query)
  1305  	if err != nil {
  1306  		t.Fatal(err)
  1307  	}
  1308  	defer func() {
  1309  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
  1310  		if _, err := playerEngine.Exec(query); err != nil {
  1311  			t.Fatal(err)
  1312  		}
  1313  		expectDeleteQueries(t)
  1314  	}()
  1315  
  1316  	expectDBClientQueries(t, qh.Expect(
  1317  		"/insert into _vt.vreplication",
  1318  		"/update _vt.vreplication set message='Picked source tablet.*",
  1319  		"begin",
  1320  		"/update _vt.vreplication set state='Stopped'",
  1321  		"commit",
  1322  	))
  1323  }
  1324  
  1325  func TestPlayerCopyTablesStopAfterCopy(t *testing.T) {
  1326  	testVcopierTestCases(t, testPlayerCopyTablesStopAfterCopy, commonVcopierTestCases())
  1327  }
  1328  
  1329  func testPlayerCopyTablesStopAfterCopy(t *testing.T) {
  1330  	defer deleteTablet(addTablet(100))
  1331  
  1332  	execStatements(t, []string{
  1333  		"create table src1(id int, val varbinary(128), primary key(id))",
  1334  		"insert into src1 values(2, 'bbb'), (1, 'aaa')",
  1335  		fmt.Sprintf("create table %s.dst1(id int, val varbinary(128), primary key(id))", vrepldb),
  1336  	})
  1337  	defer execStatements(t, []string{
  1338  		"drop table src1",
  1339  		fmt.Sprintf("drop table %s.dst1", vrepldb),
  1340  	})
  1341  	env.SchemaEngine.Reload(context.Background())
  1342  
  1343  	filter := &binlogdatapb.Filter{
  1344  		Rules: []*binlogdatapb.Rule{{
  1345  			Match:  "dst1",
  1346  			Filter: "select * from src1",
  1347  		}},
  1348  	}
  1349  
  1350  	bls := &binlogdatapb.BinlogSource{
  1351  		Keyspace:      env.KeyspaceName,
  1352  		Shard:         env.ShardName,
  1353  		Filter:        filter,
  1354  		OnDdl:         binlogdatapb.OnDDLAction_IGNORE,
  1355  		StopAfterCopy: true,
  1356  	}
  1357  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
  1358  	qr, err := playerEngine.Exec(query)
  1359  	if err != nil {
  1360  		t.Fatal(err)
  1361  	}
  1362  	defer func() {
  1363  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
  1364  		if _, err := playerEngine.Exec(query); err != nil {
  1365  			t.Fatal(err)
  1366  		}
  1367  		expectDeleteQueries(t)
  1368  	}()
  1369  
  1370  	expectDBClientQueries(t, qh.Expect(
  1371  		"/insert into _vt.vreplication",
  1372  		"/update _vt.vreplication set message='Picked source tablet.*",
  1373  		// Create the list of tables to copy and transition to Copying state.
  1374  		"begin",
  1375  		"/insert into _vt.copy_state",
  1376  		"/update _vt.vreplication set state='Copying'",
  1377  		"commit",
  1378  		// The first fast-forward has no starting point. So, it just saves the current position.
  1379  		"/update _vt.vreplication set pos=",
  1380  	).Then(qh.Eventually(
  1381  		"begin",
  1382  		"insert into dst1(id,val) values (1,'aaa'), (2,'bbb')",
  1383  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"2\\"}'.*`,
  1384  		"commit",
  1385  	)).Then(qh.Immediately(
  1386  		// copy of dst1 is done: delete from copy_state.
  1387  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst1",
  1388  		// All tables copied. Stop vreplication because we requested it.
  1389  		"/update _vt.vreplication set state='Stopped'",
  1390  	)))
  1391  
  1392  	expectData(t, "dst1", [][]string{
  1393  		{"1", "aaa"},
  1394  		{"2", "bbb"},
  1395  	})
  1396  }
  1397  
  1398  func TestPlayerCopyTableCancel(t *testing.T) {
  1399  	testVcopierTestCases(t, testPlayerCopyTableCancel, commonVcopierTestCases())
  1400  }
  1401  
  1402  func testPlayerCopyTableCancel(t *testing.T) {
  1403  	defer deleteTablet(addTablet(100))
  1404  
  1405  	execStatements(t, []string{
  1406  		"create table src1(id int, val varbinary(128), primary key(id))",
  1407  		"insert into src1 values(2, 'bbb'), (1, 'aaa')",
  1408  		fmt.Sprintf("create table %s.dst1(id int, val varbinary(128), primary key(id))", vrepldb),
  1409  	})
  1410  	defer execStatements(t, []string{
  1411  		"drop table src1",
  1412  		fmt.Sprintf("drop table %s.dst1", vrepldb),
  1413  	})
  1414  	env.SchemaEngine.Reload(context.Background())
  1415  
  1416  	saveTimeout := copyPhaseDuration
  1417  	copyPhaseDuration = 1 * time.Millisecond
  1418  	defer func() { copyPhaseDuration = saveTimeout }()
  1419  
  1420  	// Set a hook to reset the copy timeout after first call.
  1421  	vstreamRowsHook = func(ctx context.Context) {
  1422  		<-ctx.Done()
  1423  		copyPhaseDuration = saveTimeout
  1424  		vstreamRowsHook = nil
  1425  	}
  1426  
  1427  	filter := &binlogdatapb.Filter{
  1428  		Rules: []*binlogdatapb.Rule{{
  1429  			Match:  "dst1",
  1430  			Filter: "select * from src1",
  1431  		}},
  1432  	}
  1433  
  1434  	bls := &binlogdatapb.BinlogSource{
  1435  		Keyspace: env.KeyspaceName,
  1436  		Shard:    env.ShardName,
  1437  		Filter:   filter,
  1438  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
  1439  	}
  1440  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
  1441  	qr, err := playerEngine.Exec(query)
  1442  	if err != nil {
  1443  		t.Fatal(err)
  1444  	}
  1445  	defer func() {
  1446  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
  1447  		if _, err := playerEngine.Exec(query); err != nil {
  1448  			t.Fatal(err)
  1449  		}
  1450  		expectDeleteQueries(t)
  1451  	}()
  1452  
  1453  	// Make sure rows get copied in spite of the early context cancel.
  1454  	expectDBClientQueries(t, qh.Expect(
  1455  		"/insert into _vt.vreplication",
  1456  		"/update _vt.vreplication set message='Picked source tablet.*",
  1457  		// Create the list of tables to copy and transition to Copying state.
  1458  		"begin",
  1459  		"/insert into _vt.copy_state",
  1460  		"/update _vt.vreplication set state='Copying'",
  1461  		"commit",
  1462  		// The first copy will do nothing because we set the timeout to be too low.
  1463  		// The next copy should proceed as planned because we've made the timeout high again.
  1464  		// The first fast-forward has no starting point. So, it just saves the current position.
  1465  		"/update _vt.vreplication set pos=",
  1466  	).Then(qh.Eventually(
  1467  		"begin",
  1468  		"insert into dst1(id,val) values (1,'aaa'), (2,'bbb')",
  1469  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"2\\"}'.*`,
  1470  		"commit",
  1471  	)).Then(qh.Immediately(
  1472  		// copy of dst1 is done: delete from copy_state.
  1473  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst1",
  1474  		// All tables copied. Go into running state.
  1475  		"/update _vt.vreplication set state='Running'",
  1476  	)))
  1477  
  1478  	expectData(t, "dst1", [][]string{
  1479  		{"1", "aaa"},
  1480  		{"2", "bbb"},
  1481  	})
  1482  }
  1483  
  1484  func TestPlayerCopyTablesWithGeneratedColumn(t *testing.T) {
  1485  	testVcopierTestCases(t, testPlayerCopyTablesWithGeneratedColumn, commonVcopierTestCases())
  1486  }
  1487  
  1488  func testPlayerCopyTablesWithGeneratedColumn(t *testing.T) {
  1489  	flavor := strings.ToLower(env.Flavor)
  1490  	// Disable tests on percona and mariadb platforms in CI since
  1491  	// generated columns support was added in 5.7 and mariadb added mysql compatible generated columns in 10.2
  1492  	if !strings.Contains(flavor, "mysql57") && !strings.Contains(flavor, "mysql80") {
  1493  		return
  1494  	}
  1495  	defer deleteTablet(addTablet(100))
  1496  
  1497  	execStatements(t, []string{
  1498  		"create table src1(id int, val varbinary(128), val2 varbinary(128) as (concat(id, val)), val3 varbinary(128) as (concat(val, id)), id2 int, primary key(id))",
  1499  		"insert into src1(id, val, id2) values(2, 'bbb', 20), (1, 'aaa', 10)",
  1500  		fmt.Sprintf("create table %s.dst1(id int, val varbinary(128), val2 varbinary(128) as (concat(id, val)), val3 varbinary(128), id2 int, primary key(id))", vrepldb),
  1501  		"create table src2(id int, val varbinary(128), val2 varbinary(128) as (concat(id, val)), val3 varbinary(128) as (concat(val, id)), id2 int, primary key(id))",
  1502  		"insert into src2(id, val, id2) values(2, 'bbb', 20), (1, 'aaa', 10)",
  1503  		fmt.Sprintf("create table %s.dst2(val3 varbinary(128), val varbinary(128), id2 int)", vrepldb),
  1504  	})
  1505  	defer execStatements(t, []string{
  1506  		"drop table src1",
  1507  		fmt.Sprintf("drop table %s.dst1", vrepldb),
  1508  		"drop table src2",
  1509  		fmt.Sprintf("drop table %s.dst2", vrepldb),
  1510  	})
  1511  	env.SchemaEngine.Reload(context.Background())
  1512  
  1513  	filter := &binlogdatapb.Filter{
  1514  		Rules: []*binlogdatapb.Rule{{
  1515  			Match:  "dst1",
  1516  			Filter: "select * from src1",
  1517  		}, {
  1518  			Match:  "dst2",
  1519  			Filter: "select val3, val, id2 from src2",
  1520  		}},
  1521  	}
  1522  
  1523  	bls := &binlogdatapb.BinlogSource{
  1524  		Keyspace: env.KeyspaceName,
  1525  		Shard:    env.ShardName,
  1526  		Filter:   filter,
  1527  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
  1528  	}
  1529  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
  1530  	qr, err := playerEngine.Exec(query)
  1531  	if err != nil {
  1532  		t.Fatal(err)
  1533  	}
  1534  	defer func() {
  1535  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
  1536  		if _, err := playerEngine.Exec(query); err != nil {
  1537  			t.Fatal(err)
  1538  		}
  1539  		expectDeleteQueries(t)
  1540  	}()
  1541  
  1542  	expectNontxQueries(t, qh.Expect(
  1543  		"/insert into _vt.vreplication",
  1544  		"/update _vt.vreplication set message=",
  1545  		"/insert into _vt.copy_state",
  1546  		"/update _vt.vreplication set state",
  1547  		// The first fast-forward has no starting point. So, it just saves the current position.
  1548  		"insert into dst1(id,val,val3,id2) values (1,'aaa','aaa1',10), (2,'bbb','bbb2',20)",
  1549  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:<name:\\"id\\" type:INT32 > rows:<lengths:1 values:\\"2\\" > '.*`,
  1550  		// copy of dst1 is done: delete from copy_state.
  1551  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst1",
  1552  		"insert into dst2(val3,val,id2) values ('aaa1','aaa',10), ('bbb2','bbb',20)",
  1553  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:<name:\\"id\\" type:INT32 > rows:<lengths:1 values:\\"2\\" > '.*`,
  1554  		// copy of dst2 is done: delete from copy_state.
  1555  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst2",
  1556  		"/update _vt.vreplication set state",
  1557  	))
  1558  
  1559  	expectData(t, "dst1", [][]string{
  1560  		{"1", "aaa", "1aaa", "aaa1", "10"},
  1561  		{"2", "bbb", "2bbb", "bbb2", "20"},
  1562  	})
  1563  	expectData(t, "dst2", [][]string{
  1564  		{"aaa1", "aaa", "10"},
  1565  		{"bbb2", "bbb", "20"},
  1566  	})
  1567  }
  1568  
  1569  func TestCopyTablesWithInvalidDates(t *testing.T) {
  1570  	testVcopierTestCases(t, testCopyTablesWithInvalidDates, commonVcopierTestCases())
  1571  }
  1572  
  1573  func testCopyTablesWithInvalidDates(t *testing.T) {
  1574  	defer deleteTablet(addTablet(100))
  1575  
  1576  	execStatements(t, []string{
  1577  		"create table src1(id int, dt date, primary key(id))",
  1578  		fmt.Sprintf("create table %s.dst1(id int, dt date, primary key(id))", vrepldb),
  1579  		"insert into src1 values(1, '2020-01-12'), (2, '0000-00-00');",
  1580  	})
  1581  
  1582  	// default mysql flavor allows invalid dates: so disallow explicitly for this test
  1583  	if err := env.Mysqld.ExecuteSuperQuery(context.Background(), "SET @@global.sql_mode=REPLACE(REPLACE(@@session.sql_mode, 'NO_ZERO_DATE', ''), 'NO_ZERO_IN_DATE', '')"); err != nil {
  1584  		fmt.Fprintf(os.Stderr, "%v", err)
  1585  	}
  1586  	defer func() {
  1587  		if err := env.Mysqld.ExecuteSuperQuery(context.Background(), "SET @@global.sql_mode=REPLACE(@@global.sql_mode, ',NO_ZERO_DATE,NO_ZERO_IN_DATE','')"); err != nil {
  1588  			fmt.Fprintf(os.Stderr, "%v", err)
  1589  		}
  1590  	}()
  1591  	defer execStatements(t, []string{
  1592  		"drop table src1",
  1593  		fmt.Sprintf("drop table %s.dst1", vrepldb),
  1594  	})
  1595  	env.SchemaEngine.Reload(context.Background())
  1596  
  1597  	filter := &binlogdatapb.Filter{
  1598  		Rules: []*binlogdatapb.Rule{{
  1599  			Match:  "dst1",
  1600  			Filter: "select * from src1",
  1601  		}},
  1602  	}
  1603  
  1604  	bls := &binlogdatapb.BinlogSource{
  1605  		Keyspace: env.KeyspaceName,
  1606  		Shard:    env.ShardName,
  1607  		Filter:   filter,
  1608  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
  1609  	}
  1610  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
  1611  	qr, err := playerEngine.Exec(query)
  1612  	require.NoError(t, err)
  1613  
  1614  	expectDBClientQueries(t, qh.Expect(
  1615  		"/insert into _vt.vreplication",
  1616  		"/update _vt.vreplication set message='Picked source tablet.*",
  1617  		// Create the list of tables to copy and transition to Copying state.
  1618  		"begin",
  1619  		"/insert into _vt.copy_state",
  1620  		"/update _vt.vreplication set state='Copying'",
  1621  		"commit",
  1622  		// The first fast-forward has no starting point. So, it just saves the current position.
  1623  		"/update _vt.vreplication set pos=",
  1624  	).Then(qh.Eventually(
  1625  		"begin",
  1626  		"insert into dst1(id,dt) values (1,'2020-01-12'), (2,'0000-00-00')",
  1627  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} rows:{lengths:1 values:\\"2\\"}'.*`,
  1628  		"commit",
  1629  	)).Then(qh.Immediately(
  1630  		// copy of dst1 is done: delete from copy_state.
  1631  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst1",
  1632  		// All tables copied. Final catch up followed by Running state.
  1633  		"/update _vt.vreplication set state='Running'",
  1634  	)))
  1635  
  1636  	expectData(t, "dst1", [][]string{
  1637  		{"1", "2020-01-12"},
  1638  		{"2", "0000-00-00"},
  1639  	})
  1640  
  1641  	query = fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
  1642  	if _, err := playerEngine.Exec(query); err != nil {
  1643  		t.Fatal(err)
  1644  	}
  1645  	expectDBClientQueries(t, qh.Expect(
  1646  		"begin",
  1647  		"/delete from _vt.vreplication",
  1648  		"/delete from _vt.copy_state",
  1649  		"/delete from _vt.post_copy_action",
  1650  		"commit",
  1651  	))
  1652  }
  1653  
  1654  func supportsInvisibleColumns() bool {
  1655  	if env.DBType == string(mysqlctl.FlavorMySQL) && env.DBMajorVersion >= 8 &&
  1656  		(env.DBMinorVersion > 0 || env.DBPatchVersion >= 23) {
  1657  		return true
  1658  	}
  1659  	log.Infof("invisible columns not supported in %d.%d.%d", env.DBMajorVersion, env.DBMinorVersion, env.DBPatchVersion)
  1660  	return false
  1661  }
  1662  
  1663  func TestCopyInvisibleColumns(t *testing.T) {
  1664  	testVcopierTestCases(t, testCopyInvisibleColumns, commonVcopierTestCases())
  1665  }
  1666  
  1667  func testCopyInvisibleColumns(t *testing.T) {
  1668  	if !supportsInvisibleColumns() {
  1669  		t.Skip()
  1670  	}
  1671  
  1672  	defer deleteTablet(addTablet(100))
  1673  
  1674  	execStatements(t, []string{
  1675  		"create table src1(id int, id2 int, inv1 int invisible, inv2 int invisible, primary key(id, inv1))",
  1676  		"insert into src1(id, id2, inv1, inv2) values(2, 20, 200, 2000), (1, 10, 100, 1000)",
  1677  		fmt.Sprintf("create table %s.dst1(id int, id2 int, inv1 int invisible, inv2 int invisible, primary key(id, inv1))", vrepldb),
  1678  	})
  1679  	defer execStatements(t, []string{
  1680  		"drop table src1",
  1681  		fmt.Sprintf("drop table %s.dst1", vrepldb),
  1682  	})
  1683  	env.SchemaEngine.Reload(context.Background())
  1684  
  1685  	filter := &binlogdatapb.Filter{
  1686  		Rules: []*binlogdatapb.Rule{{
  1687  			Match:  "dst1",
  1688  			Filter: "select * from src1",
  1689  		}},
  1690  	}
  1691  
  1692  	bls := &binlogdatapb.BinlogSource{
  1693  		Keyspace: env.KeyspaceName,
  1694  		Shard:    env.ShardName,
  1695  		Filter:   filter,
  1696  		OnDdl:    binlogdatapb.OnDDLAction_IGNORE,
  1697  	}
  1698  	query := binlogplayer.CreateVReplicationState("test", bls, "", binlogplayer.VReplicationInit, playerEngine.dbName, 0, 0)
  1699  	qr, err := playerEngine.Exec(query)
  1700  	if err != nil {
  1701  		t.Fatal(err)
  1702  	}
  1703  	defer func() {
  1704  		query := fmt.Sprintf("delete from _vt.vreplication where id = %d", qr.InsertID)
  1705  		if _, err := playerEngine.Exec(query); err != nil {
  1706  			t.Fatal(err)
  1707  		}
  1708  		expectDeleteQueries(t)
  1709  	}()
  1710  
  1711  	expectNontxQueries(t, qh.Expect(
  1712  		"/insert into _vt.vreplication",
  1713  		"/update _vt.vreplication set message=",
  1714  		"/insert into _vt.copy_state",
  1715  		"/update _vt.vreplication set state='Copying'",
  1716  		// The first fast-forward has no starting point. So, it just saves the current position.
  1717  		"insert into dst1(id,id2,inv1,inv2) values (1,10,100,1000), (2,20,200,2000)",
  1718  		`/insert into _vt.copy_state \(lastpk, vrepl_id, table_name\) values \('fields:{name:\\"id\\" type:INT32} fields:{name:\\"inv1\\" type:INT32} rows:{lengths:1 lengths:3 values:\\"2200\\"}'.*`,
  1719  		// copy of dst1 is done: delete from copy_state.
  1720  		"/delete cs, pca from _vt.copy_state as cs left join _vt.post_copy_action as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name.*dst1",
  1721  		"/update _vt.vreplication set state='Running'",
  1722  	))
  1723  
  1724  	expectData(t, "dst1", [][]string{
  1725  		{"1", "10"},
  1726  		{"2", "20"},
  1727  	})
  1728  	expectQueryResult(t, "select id,id2,inv1,inv2 from vrepl.dst1", [][]string{
  1729  		{"1", "10", "100", "1000"},
  1730  		{"2", "20", "200", "2000"},
  1731  	})
  1732  }