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 }