vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/vstreamer/vstreamer_flaky_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 vstreamer 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "strconv" 24 "strings" 25 "sync" 26 "testing" 27 "time" 28 29 "google.golang.org/protobuf/proto" 30 31 "vitess.io/vitess/go/vt/log" 32 "vitess.io/vitess/go/vt/sqlparser" 33 34 "github.com/stretchr/testify/assert" 35 "github.com/stretchr/testify/require" 36 37 "vitess.io/vitess/go/mysql" 38 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 39 ) 40 41 type testcase struct { 42 input any 43 output [][]string 44 } 45 46 func checkIfOptionIsSupported(t *testing.T, variable string) bool { 47 qr, err := env.Mysqld.FetchSuperQuery(context.Background(), fmt.Sprintf("show variables like '%s'", variable)) 48 require.NoError(t, err) 49 require.NotNil(t, qr) 50 if qr.Rows != nil && len(qr.Rows) == 1 { 51 return true 52 } 53 return false 54 } 55 56 type TestColumn struct { 57 name, dataType, colType string 58 len, charset int64 59 } 60 61 type TestFieldEvent struct { 62 table, db string 63 cols []*TestColumn 64 } 65 66 func (tfe *TestFieldEvent) String() string { 67 s := fmt.Sprintf("type:FIELD field_event:{table_name:\"%s\"", tfe.table) 68 fld := "" 69 for _, col := range tfe.cols { 70 fld += fmt.Sprintf(" fields:{name:\"%s\" type:%s table:\"%s\" org_table:\"%s\" database:\"%s\" org_name:\"%s\" column_length:%d charset:%d", 71 col.name, col.dataType, tfe.table, tfe.table, tfe.db, col.name, col.len, col.charset) 72 if col.colType != "" { 73 fld += fmt.Sprintf(" column_type:\"%s\"", col.colType) 74 } 75 fld += "}" 76 } 77 s += fld 78 s += "}" 79 return s 80 } 81 82 func TestSetAndEnum(t *testing.T) { 83 execStatements(t, []string{ 84 "create table t1(id int, val binary(4), color set('red','green','blue'), size enum('S','M','L'), primary key(id))", 85 }) 86 defer execStatements(t, []string{ 87 "drop table t1", 88 }) 89 engine.se.Reload(context.Background()) 90 queries := []string{ 91 "begin", 92 "insert into t1 values (1, 'aaa', 'red,blue', 'S')", 93 "insert into t1 values (2, 'bbb', 'green', 'M')", 94 "insert into t1 values (3, 'ccc', 'red,blue,green', 'L')", 95 "commit", 96 } 97 98 fe := &TestFieldEvent{ 99 table: "t1", 100 db: "vttest", 101 cols: []*TestColumn{ 102 {name: "id", dataType: "INT32", colType: "int(11)", len: 11, charset: 63}, 103 {name: "val", dataType: "BINARY", colType: "binary(4)", len: 4, charset: 63}, 104 {name: "color", dataType: "SET", colType: "set('red','green','blue')", len: 56, charset: 45}, 105 {name: "size", dataType: "ENUM", colType: "enum('S','M','L')", len: 4, charset: 45}, 106 }, 107 } 108 109 testcases := []testcase{{ 110 input: queries, 111 output: [][]string{{ 112 `begin`, 113 fe.String(), 114 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:4 lengths:1 lengths:1 values:"1aaa\x0051"}}}`, 115 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:4 lengths:1 lengths:1 values:"2bbb\x0022"}}}`, 116 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:4 lengths:1 lengths:1 values:"3ccc\x0073"}}}`, 117 `gtid`, 118 `commit`, 119 }}, 120 }} 121 runCases(t, nil, testcases, "current", nil) 122 } 123 124 func TestCellValuePadding(t *testing.T) { 125 126 execStatements(t, []string{ 127 "create table t1(id int, val binary(4), primary key(val))", 128 "create table t2(id int, val char(4), primary key(val))", 129 "create table t3(id int, val char(4) collate utf8mb4_bin, primary key(val))", 130 }) 131 defer execStatements(t, []string{ 132 "drop table t1", 133 "drop table t2", 134 "drop table t3", 135 }) 136 engine.se.Reload(context.Background()) 137 queries := []string{ 138 "begin", 139 "insert into t1 values (1, 'aaa\000')", 140 "insert into t1 values (2, 'bbb\000')", 141 "update t1 set id = 11 where val = 'aaa\000'", 142 "insert into t2 values (1, 'aaa')", 143 "insert into t2 values (2, 'bbb')", 144 "update t2 set id = 11 where val = 'aaa'", 145 "insert into t3 values (1, 'aaa')", 146 "insert into t3 values (2, 'bb')", 147 "update t3 set id = 11 where val = 'aaa'", 148 "commit", 149 } 150 151 testcases := []testcase{{ 152 input: queries, 153 output: [][]string{{ 154 `begin`, 155 `type:FIELD field_event:{table_name:"t1" fields:{name:"id" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:BINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:4 charset:63 column_type:"binary(4)"}}`, 156 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:4 values:"1aaa\x00"}}}`, 157 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:4 values:"2bbb\x00"}}}`, 158 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:4 values:"1aaa\x00"} after:{lengths:2 lengths:4 values:"11aaa\x00"}}}`, 159 `type:FIELD field_event:{table_name:"t2" fields:{name:"id" type:INT32 table:"t2" org_table:"t2" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:CHAR table:"t2" org_table:"t2" database:"vttest" org_name:"val" column_length:16 charset:45 column_type:"char(4)"}}`, 160 `type:ROW row_event:{table_name:"t2" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 161 `type:ROW row_event:{table_name:"t2" row_changes:{after:{lengths:1 lengths:3 values:"2bbb"}}}`, 162 `type:ROW row_event:{table_name:"t2" row_changes:{before:{lengths:1 lengths:3 values:"1aaa"} after:{lengths:2 lengths:3 values:"11aaa"}}}`, 163 `type:FIELD field_event:{table_name:"t3" fields:{name:"id" type:INT32 table:"t3" org_table:"t3" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:BINARY table:"t3" org_table:"t3" database:"vttest" org_name:"val" column_length:16 charset:45 column_type:"char(4)"}}`, 164 `type:ROW row_event:{table_name:"t3" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 165 `type:ROW row_event:{table_name:"t3" row_changes:{after:{lengths:1 lengths:2 values:"2bb"}}}`, 166 `type:ROW row_event:{table_name:"t3" row_changes:{before:{lengths:1 lengths:3 values:"1aaa"} after:{lengths:2 lengths:3 values:"11aaa"}}}`, 167 `gtid`, 168 `commit`, 169 }}, 170 }} 171 runCases(t, nil, testcases, "current", nil) 172 } 173 174 func TestSetStatement(t *testing.T) { 175 176 if testing.Short() { 177 t.Skip() 178 } 179 if !checkIfOptionIsSupported(t, "log_builtin_as_identified_by_password") { 180 // the combination of setting this option and support for "set password" only works on a few flavors 181 log.Info("Cannot test SetStatement on this flavor") 182 return 183 } 184 engine.se.Reload(context.Background()) 185 186 execStatements(t, []string{ 187 "create table t1(id int, val varbinary(128), primary key(id))", 188 }) 189 defer execStatements(t, []string{ 190 "drop table t1", 191 }) 192 engine.se.Reload(context.Background()) 193 queries := []string{ 194 "begin", 195 "insert into t1 values (1, 'aaa')", 196 "commit", 197 "set global log_builtin_as_identified_by_password=1", 198 "SET PASSWORD FOR 'vt_appdebug'@'localhost'='*AA17DA66C7C714557F5485E84BCAFF2C209F2F53'", //select password('vtappdebug_password'); 199 } 200 testcases := []testcase{{ 201 input: queries, 202 output: [][]string{{ 203 `begin`, 204 `type:FIELD field_event:{table_name:"t1" fields:{name:"id" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 205 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 206 `gtid`, 207 `commit`, 208 }, { 209 `gtid`, 210 `other`, 211 }}, 212 }} 213 runCases(t, nil, testcases, "current", nil) 214 } 215 216 func TestStmtComment(t *testing.T) { 217 218 if testing.Short() { 219 t.Skip() 220 } 221 222 execStatements(t, []string{ 223 "create table t1(id int, val varbinary(128), primary key(id))", 224 }) 225 defer execStatements(t, []string{ 226 "drop table t1", 227 }) 228 engine.se.Reload(context.Background()) 229 queries := []string{ 230 "begin", 231 "insert into t1 values (1, 'aaa')", 232 "commit", 233 "/*!40000 ALTER TABLE `t1` DISABLE KEYS */", 234 } 235 testcases := []testcase{{ 236 input: queries, 237 output: [][]string{{ 238 `begin`, 239 `type:FIELD field_event:{table_name:"t1" fields:{name:"id" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 240 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 241 `gtid`, 242 `commit`, 243 }, { 244 `gtid`, 245 `other`, 246 }}, 247 }} 248 runCases(t, nil, testcases, "current", nil) 249 } 250 251 func TestVersion(t *testing.T) { 252 if testing.Short() { 253 t.Skip() 254 } 255 256 oldEngine := engine 257 defer func() { 258 engine = oldEngine 259 }() 260 261 err := env.SchemaEngine.EnableHistorian(true) 262 require.NoError(t, err) 263 defer env.SchemaEngine.EnableHistorian(false) 264 265 engine = NewEngine(engine.env, env.SrvTopo, env.SchemaEngine, nil, env.Cells[0]) 266 engine.InitDBConfig(env.KeyspaceName, env.ShardName) 267 engine.Open() 268 defer engine.Close() 269 270 execStatements(t, []string{ 271 "create database if not exists _vt", 272 "create table if not exists _vt.schema_version(id int, pos varbinary(10000), time_updated bigint(20), ddl varchar(10000), schemax blob, primary key(id))", 273 }) 274 defer execStatements(t, []string{ 275 "drop table _vt.schema_version", 276 }) 277 dbSchema := &binlogdatapb.MinimalSchema{ 278 Tables: []*binlogdatapb.MinimalTable{{ 279 Name: "t1", 280 }}, 281 } 282 blob, _ := proto.Marshal(dbSchema) 283 engine.se.Reload(context.Background()) 284 gtid := "MariaDB/0-41983-20" 285 testcases := []testcase{{ 286 input: []string{ 287 fmt.Sprintf("insert into _vt.schema_version values(1, '%s', 123, 'create table t1', %v)", gtid, encodeString(string(blob))), 288 }, 289 // External table events don't get sent. 290 output: [][]string{{ 291 `begin`, 292 `type:VERSION`}, { 293 `gtid`, 294 `commit`}}, 295 }} 296 runCases(t, nil, testcases, "", nil) 297 mt, err := env.SchemaEngine.GetTableForPos(sqlparser.NewIdentifierCS("t1"), gtid) 298 require.NoError(t, err) 299 assert.True(t, proto.Equal(mt, dbSchema.Tables[0])) 300 } 301 302 func insertLotsOfData(t *testing.T, numRows int) { 303 query1 := "insert into t1 (id11, id12) values" 304 s := "" 305 for i := 1; i <= numRows; i++ { 306 if s != "" { 307 s += "," 308 } 309 s += fmt.Sprintf("(%d,%d)", i, i*10) 310 } 311 query1 += s 312 query2 := "insert into t2 (id21, id22) values" 313 s = "" 314 for i := 1; i <= numRows; i++ { 315 if s != "" { 316 s += "," 317 } 318 s += fmt.Sprintf("(%d,%d)", i, i*20) 319 } 320 query2 += s 321 execStatements(t, []string{ 322 query1, 323 query2, 324 }) 325 } 326 327 func TestMissingTables(t *testing.T) { 328 if testing.Short() { 329 t.Skip() 330 } 331 engine.se.Reload(context.Background()) 332 execStatements(t, []string{ 333 "create table t1(id11 int, id12 int, primary key(id11))", 334 "create table shortlived(id31 int, id32 int, primary key(id31))", 335 }) 336 defer execStatements(t, []string{ 337 "drop table t1", 338 "drop table _shortlived", 339 }) 340 startPos := primaryPosition(t) 341 execStatements(t, []string{ 342 "insert into shortlived values (1,1), (2,2)", 343 "alter table shortlived rename to _shortlived", 344 }) 345 engine.se.Reload(context.Background()) 346 filter := &binlogdatapb.Filter{ 347 Rules: []*binlogdatapb.Rule{{ 348 Match: "t1", 349 Filter: "select * from t1", 350 }}, 351 } 352 testcases := []testcase{ 353 { 354 input: []string{}, 355 output: [][]string{}, 356 }, 357 358 { 359 input: []string{ 360 "insert into t1 values (101, 1010)", 361 }, 362 output: [][]string{ 363 { 364 "begin", 365 "gtid", 366 "commit", 367 }, 368 { 369 "gtid", 370 "type:OTHER", 371 }, 372 { 373 "begin", 374 "type:FIELD field_event:{table_name:\"t1\" fields:{name:\"id11\" type:INT32 table:\"t1\" org_table:\"t1\" database:\"vttest\" org_name:\"id11\" column_length:11 charset:63 column_type:\"int(11)\"} fields:{name:\"id12\" type:INT32 table:\"t1\" org_table:\"t1\" database:\"vttest\" org_name:\"id12\" column_length:11 charset:63 column_type:\"int(11)\"}}", 375 "type:ROW row_event:{table_name:\"t1\" row_changes:{after:{lengths:3 lengths:4 values:\"1011010\"}}}", 376 "gtid", 377 "commit", 378 }, 379 }, 380 }, 381 } 382 runCases(t, filter, testcases, startPos, nil) 383 } 384 385 func TestVStreamCopySimpleFlow(t *testing.T) { 386 if testing.Short() { 387 t.Skip() 388 } 389 execStatements(t, []string{ 390 "create table t1(id11 int, id12 int, primary key(id11))", 391 "create table t2(id21 int, id22 int, primary key(id21))", 392 }) 393 log.Infof("Pos before bulk insert: %s", primaryPosition(t)) 394 insertLotsOfData(t, 10) 395 log.Infof("Pos after bulk insert: %s", primaryPosition(t)) 396 defer execStatements(t, []string{ 397 "drop table t1", 398 "drop table t2", 399 }) 400 engine.se.Reload(context.Background()) 401 ctx := context.Background() 402 qr, err := env.Mysqld.FetchSuperQuery(ctx, "SELECT count(*) as cnt from t1, t2 where t1.id11 = t2.id21") 403 if err != nil { 404 t.Fatal("Query failed") 405 } 406 require.Equal(t, "[[INT64(10)]]", fmt.Sprintf("%v", qr.Rows)) 407 408 filter := &binlogdatapb.Filter{ 409 Rules: []*binlogdatapb.Rule{{ 410 Match: "t1", 411 Filter: "select * from t1", 412 }, { 413 Match: "t2", 414 Filter: "select * from t2", 415 }}, 416 } 417 418 var tablePKs []*binlogdatapb.TableLastPK 419 tablePKs = append(tablePKs, getTablePK("t1", 1)) 420 tablePKs = append(tablePKs, getTablePK("t2", 2)) 421 422 t1FieldEvent := []string{"begin", "type:FIELD field_event:{table_name:\"t1\" fields:{name:\"id11\" type:INT32 table:\"t1\" org_table:\"t1\" database:\"vttest\" org_name:\"id11\" column_length:11 charset:63} fields:{name:\"id12\" type:INT32 table:\"t1\" org_table:\"t1\" database:\"vttest\" org_name:\"id12\" column_length:11 charset:63}}"} 423 t2FieldEvent := []string{"begin", "type:FIELD field_event:{table_name:\"t2\" fields:{name:\"id21\" type:INT32 table:\"t2\" org_table:\"t2\" database:\"vttest\" org_name:\"id21\" column_length:11 charset:63} fields:{name:\"id22\" type:INT32 table:\"t2\" org_table:\"t2\" database:\"vttest\" org_name:\"id22\" column_length:11 charset:63}}"} 424 t1Events := []string{} 425 t2Events := []string{} 426 for i := 1; i <= 10; i++ { 427 t1Events = append(t1Events, 428 fmt.Sprintf("type:ROW row_event:{table_name:\"t1\" row_changes:{after:{lengths:%d lengths:%d values:\"%d%d\"}}}", len(strconv.Itoa(i)), len(strconv.Itoa(i*10)), i, i*10)) 429 t2Events = append(t2Events, 430 fmt.Sprintf("type:ROW row_event:{table_name:\"t2\" row_changes:{after:{lengths:%d lengths:%d values:\"%d%d\"}}}", len(strconv.Itoa(i)), len(strconv.Itoa(i*20)), i, i*20)) 431 } 432 t1Events = append(t1Events, "lastpk", "commit") 433 t2Events = append(t2Events, "lastpk", "commit") 434 435 insertEvents1 := []string{ 436 "begin", 437 "type:FIELD field_event:{table_name:\"t1\" fields:{name:\"id11\" type:INT32 table:\"t1\" org_table:\"t1\" database:\"vttest\" org_name:\"id11\" column_length:11 charset:63 column_type:\"int(11)\"} fields:{name:\"id12\" type:INT32 table:\"t1\" org_table:\"t1\" database:\"vttest\" org_name:\"id12\" column_length:11 charset:63 column_type:\"int(11)\"}}", 438 "type:ROW row_event:{table_name:\"t1\" row_changes:{after:{lengths:3 lengths:4 values:\"1011010\"}}}", 439 "gtid", 440 "commit"} 441 insertEvents2 := []string{ 442 "begin", 443 "type:FIELD field_event:{table_name:\"t2\" fields:{name:\"id21\" type:INT32 table:\"t2\" org_table:\"t2\" database:\"vttest\" org_name:\"id21\" column_length:11 charset:63 column_type:\"int(11)\"} fields:{name:\"id22\" type:INT32 table:\"t2\" org_table:\"t2\" database:\"vttest\" org_name:\"id22\" column_length:11 charset:63 column_type:\"int(11)\"}}", 444 "type:ROW row_event:{table_name:\"t2\" row_changes:{after:{lengths:3 lengths:4 values:\"2022020\"}}}", 445 "gtid", 446 "commit"} 447 448 testcases := []testcase{ 449 { 450 input: []string{}, 451 output: [][]string{t1FieldEvent, {"gtid"}, t1Events, {"begin", "lastpk", "commit"}, t2FieldEvent, t2Events, {"begin", "lastpk", "commit"}, {"copy_completed"}}, 452 }, 453 454 { 455 input: []string{ 456 "insert into t1 values (101, 1010)", 457 }, 458 output: [][]string{insertEvents1}, 459 }, 460 { 461 input: []string{ 462 "insert into t2 values (202, 2020)", 463 }, 464 output: [][]string{insertEvents2}, 465 }, 466 } 467 468 runCases(t, filter, testcases, "vscopy", tablePKs) 469 log.Infof("Pos at end of test: %s", primaryPosition(t)) 470 } 471 472 func TestVStreamCopyWithDifferentFilters(t *testing.T) { 473 if testing.Short() { 474 t.Skip() 475 } 476 execStatements(t, []string{ 477 "create table t1(id1 int, id2 int, id3 int, primary key(id1))", 478 "create table t2a(id1 int, id2 int, primary key(id1))", 479 "create table t2b(id1 varchar(20), id2 int, primary key(id1))", 480 }) 481 defer execStatements(t, []string{ 482 "drop table t1", 483 "drop table t2a", 484 "drop table t2b", 485 }) 486 engine.se.Reload(context.Background()) 487 ctx, cancel := context.WithCancel(context.Background()) 488 defer cancel() 489 filter := &binlogdatapb.Filter{ 490 Rules: []*binlogdatapb.Rule{{ 491 Match: "/t2.*", 492 }, { 493 Match: "t1", 494 Filter: "select id1, id2 from t1", 495 }}, 496 } 497 498 execStatements(t, []string{ 499 "insert into t1(id1, id2, id3) values (1, 2, 3)", 500 "insert into t2a(id1, id2) values (1, 4)", 501 "insert into t2b(id1, id2) values ('b', 6)", 502 "insert into t2b(id1, id2) values ('a', 5)", 503 }) 504 505 var expectedEvents = []string{ 506 "type:BEGIN", 507 "type:FIELD field_event:{table_name:\"t1\" fields:{name:\"id1\" type:INT32 table:\"t1\" org_table:\"t1\" database:\"vttest\" org_name:\"id1\" column_length:11 charset:63} fields:{name:\"id2\" type:INT32 table:\"t1\" org_table:\"t1\" database:\"vttest\" org_name:\"id2\" column_length:11 charset:63}}", 508 "type:GTID", 509 "type:ROW row_event:{table_name:\"t1\" row_changes:{after:{lengths:1 lengths:1 values:\"12\"}}}", 510 "type:LASTPK last_p_k_event:{table_last_p_k:{table_name:\"t1\" lastpk:{rows:{lengths:1 values:\"1\"}}}}", 511 "type:COMMIT", 512 "type:BEGIN", 513 "type:LASTPK last_p_k_event:{table_last_p_k:{table_name:\"t1\"} completed:true}", 514 "type:COMMIT", 515 "type:BEGIN", 516 "type:FIELD field_event:{table_name:\"t2a\" fields:{name:\"id1\" type:INT32 table:\"t2a\" org_table:\"t2a\" database:\"vttest\" org_name:\"id1\" column_length:11 charset:63} fields:{name:\"id2\" type:INT32 table:\"t2a\" org_table:\"t2a\" database:\"vttest\" org_name:\"id2\" column_length:11 charset:63}}", 517 "type:ROW row_event:{table_name:\"t2a\" row_changes:{after:{lengths:1 lengths:1 values:\"14\"}}}", 518 "type:LASTPK last_p_k_event:{table_last_p_k:{table_name:\"t2a\" lastpk:{rows:{lengths:1 values:\"1\"}}}}", 519 "type:COMMIT", 520 "type:BEGIN", 521 "type:LASTPK last_p_k_event:{table_last_p_k:{table_name:\"t2a\"} completed:true}", 522 "type:COMMIT", 523 "type:BEGIN", 524 "type:FIELD field_event:{table_name:\"t2b\" fields:{name:\"id1\" type:VARCHAR table:\"t2b\" org_table:\"t2b\" database:\"vttest\" org_name:\"id1\" column_length:80 charset:45} fields:{name:\"id2\" type:INT32 table:\"t2b\" org_table:\"t2b\" database:\"vttest\" org_name:\"id2\" column_length:11 charset:63}}", 525 "type:ROW row_event:{table_name:\"t2b\" row_changes:{after:{lengths:1 lengths:1 values:\"a5\"}}}", 526 "type:ROW row_event:{table_name:\"t2b\" row_changes:{after:{lengths:1 lengths:1 values:\"b6\"}}}", 527 "type:LASTPK last_p_k_event:{table_last_p_k:{table_name:\"t2b\" lastpk:{rows:{lengths:1 values:\"b\"}}}}", 528 "type:COMMIT", 529 "type:BEGIN", 530 "type:LASTPK last_p_k_event:{table_last_p_k:{table_name:\"t2b\"} completed:true}", 531 "type:COMMIT", 532 } 533 534 var allEvents []*binlogdatapb.VEvent 535 var wg sync.WaitGroup 536 wg.Add(1) 537 ctx2, cancel2 := context.WithDeadline(ctx, time.Now().Add(10*time.Second)) 538 defer cancel2() 539 540 var errGoroutine error 541 go func() { 542 defer wg.Done() 543 engine.Stream(ctx2, "", nil, filter, func(evs []*binlogdatapb.VEvent) error { 544 for _, ev := range evs { 545 if ev.Type == binlogdatapb.VEventType_HEARTBEAT { 546 continue 547 } 548 if ev.Throttled { 549 continue 550 } 551 allEvents = append(allEvents, ev) 552 } 553 if len(allEvents) == len(expectedEvents) { 554 log.Infof("Got %d events as expected", len(allEvents)) 555 for i, ev := range allEvents { 556 ev.Timestamp = 0 557 if ev.Type == binlogdatapb.VEventType_FIELD { 558 for j := range ev.FieldEvent.Fields { 559 ev.FieldEvent.Fields[j].Flags = 0 560 } 561 ev.FieldEvent.Keyspace = "" 562 ev.FieldEvent.Shard = "" 563 } 564 if ev.Type == binlogdatapb.VEventType_ROW { 565 ev.RowEvent.Keyspace = "" 566 ev.RowEvent.Shard = "" 567 } 568 got := ev.String() 569 want := expectedEvents[i] 570 if !strings.HasPrefix(got, want) { 571 errGoroutine = fmt.Errorf("Event %d did not match, want %s, got %s", i, want, got) 572 return errGoroutine 573 } 574 } 575 576 return io.EOF 577 } 578 return nil 579 }) 580 }() 581 wg.Wait() 582 if errGoroutine != nil { 583 t.Fatalf(errGoroutine.Error()) 584 } 585 } 586 587 func TestFilteredVarBinary(t *testing.T) { 588 if testing.Short() { 589 t.Skip() 590 } 591 592 execStatements(t, []string{ 593 "create table t1(id1 int, val varbinary(128), primary key(id1))", 594 }) 595 defer execStatements(t, []string{ 596 "drop table t1", 597 }) 598 engine.se.Reload(context.Background()) 599 600 filter := &binlogdatapb.Filter{ 601 Rules: []*binlogdatapb.Rule{{ 602 Match: "t1", 603 Filter: "select id1, val from t1 where val = 'newton'", 604 }}, 605 } 606 607 testcases := []testcase{{ 608 input: []string{ 609 "begin", 610 "insert into t1 values (1, 'kepler')", 611 "insert into t1 values (2, 'newton')", 612 "insert into t1 values (3, 'newton')", 613 "insert into t1 values (4, 'kepler')", 614 "insert into t1 values (5, 'newton')", 615 "update t1 set val = 'newton' where id1 = 1", 616 "update t1 set val = 'kepler' where id1 = 2", 617 "update t1 set val = 'newton' where id1 = 2", 618 "update t1 set val = 'kepler' where id1 = 1", 619 "delete from t1 where id1 in (2,3)", 620 "commit", 621 }, 622 output: [][]string{{ 623 `begin`, 624 `type:FIELD field_event:{table_name:"t1" fields:{name:"id1" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id1" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 625 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:6 values:"2newton"}}}`, 626 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:6 values:"3newton"}}}`, 627 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:6 values:"5newton"}}}`, 628 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:6 values:"1newton"}}}`, 629 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:6 values:"2newton"}}}`, 630 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:6 values:"2newton"}}}`, 631 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:6 values:"1newton"}}}`, 632 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:6 values:"2newton"}} row_changes:{before:{lengths:1 lengths:6 values:"3newton"}}}`, 633 `gtid`, 634 `commit`, 635 }}, 636 }} 637 runCases(t, filter, testcases, "", nil) 638 } 639 640 func TestFilteredInt(t *testing.T) { 641 if testing.Short() { 642 t.Skip() 643 } 644 engine.se.Reload(context.Background()) 645 646 execStatements(t, []string{ 647 "create table t1(id1 int, id2 int, val varbinary(128), primary key(id1))", 648 }) 649 defer execStatements(t, []string{ 650 "drop table t1", 651 }) 652 engine.se.Reload(context.Background()) 653 654 filter := &binlogdatapb.Filter{ 655 Rules: []*binlogdatapb.Rule{{ 656 Match: "t1", 657 Filter: "select id1, val from t1 where id2 = 200", 658 }}, 659 } 660 661 testcases := []testcase{{ 662 input: []string{ 663 "begin", 664 "insert into t1 values (1, 100, 'aaa')", 665 "insert into t1 values (2, 200, 'bbb')", 666 "insert into t1 values (3, 100, 'ccc')", 667 "insert into t1 values (4, 200, 'ddd')", 668 "insert into t1 values (5, 200, 'eee')", 669 "update t1 set val = 'newddd' where id1 = 4", 670 "update t1 set id2 = 200 where id1 = 1", 671 "update t1 set id2 = 100 where id1 = 2", 672 "update t1 set id2 = 100 where id1 = 1", 673 "update t1 set id2 = 200 where id1 = 2", 674 "commit", 675 }, 676 output: [][]string{{ 677 `begin`, 678 `type:FIELD field_event:{table_name:"t1" fields:{name:"id1" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id1" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 679 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:3 values:"2bbb"}}}`, 680 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:3 values:"4ddd"}}}`, 681 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:3 values:"5eee"}}}`, 682 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:3 values:"4ddd"} after:{lengths:1 lengths:6 values:"4newddd"}}}`, 683 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 684 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:3 values:"2bbb"}}}`, 685 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:3 values:"1aaa"}}}`, 686 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:3 values:"2bbb"}}}`, 687 `gtid`, 688 `commit`, 689 }}, 690 }} 691 runCases(t, filter, testcases, "", nil) 692 } 693 694 func TestSavepoint(t *testing.T) { 695 if testing.Short() { 696 t.Skip() 697 } 698 699 execStatements(t, []string{ 700 "create table stream1(id int, val varbinary(128), primary key(id))", 701 "create table stream2(id int, val varbinary(128), primary key(id))", 702 }) 703 defer execStatements(t, []string{ 704 "drop table stream1", 705 "drop table stream2", 706 }) 707 engine.se.Reload(context.Background()) 708 testcases := []testcase{{ 709 input: []string{ 710 "begin", 711 "insert into stream1 values (1, 'aaa')", 712 "savepoint a", 713 "insert into stream1 values (2, 'aaa')", 714 "rollback work to savepoint a", 715 "savepoint b", 716 "update stream1 set val='bbb' where id = 1", 717 "release savepoint b", 718 "commit", 719 }, 720 output: [][]string{{ 721 `begin`, 722 `type:FIELD field_event:{table_name:"stream1" fields:{name:"id" type:INT32 table:"stream1" org_table:"stream1" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"stream1" org_table:"stream1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 723 `type:ROW row_event:{table_name:"stream1" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 724 `type:ROW row_event:{table_name:"stream1" row_changes:{before:{lengths:1 lengths:3 values:"1aaa"} after:{lengths:1 lengths:3 values:"1bbb"}}}`, 725 `gtid`, 726 `commit`, 727 }}, 728 }} 729 runCases(t, nil, testcases, "current", nil) 730 } 731 732 func TestSavepointWithFilter(t *testing.T) { 733 if testing.Short() { 734 t.Skip() 735 } 736 737 execStatements(t, []string{ 738 "create table stream1(id int, val varbinary(128), primary key(id))", 739 "create table stream2(id int, val varbinary(128), primary key(id))", 740 }) 741 defer execStatements(t, []string{ 742 "drop table stream1", 743 "drop table stream2", 744 }) 745 engine.se.Reload(context.Background()) 746 testcases := []testcase{{ 747 input: []string{ 748 "begin", 749 "insert into stream1 values (1, 'aaa')", 750 "savepoint a", 751 "insert into stream1 values (2, 'aaa')", 752 "savepoint b", 753 "insert into stream1 values (3, 'aaa')", 754 "savepoint c", 755 "insert into stream1 values (4, 'aaa')", 756 "savepoint d", 757 "commit", 758 759 "begin", 760 "insert into stream1 values (5, 'aaa')", 761 "savepoint d", 762 "insert into stream1 values (6, 'aaa')", 763 "savepoint c", 764 "insert into stream1 values (7, 'aaa')", 765 "savepoint b", 766 "insert into stream1 values (8, 'aaa')", 767 "savepoint a", 768 "commit", 769 770 "begin", 771 "insert into stream1 values (9, 'aaa')", 772 "savepoint a", 773 "insert into stream2 values (1, 'aaa')", 774 "savepoint b", 775 "insert into stream1 values (10, 'aaa')", 776 "savepoint c", 777 "insert into stream2 values (2, 'aaa')", 778 "savepoint d", 779 "commit", 780 }, 781 output: [][]string{{ 782 `begin`, 783 `gtid`, 784 `commit`, 785 }, { 786 `begin`, 787 `gtid`, 788 `commit`, 789 }, { 790 `begin`, 791 `type:FIELD field_event:{table_name:"stream2" fields:{name:"id" type:INT32 table:"stream2" org_table:"stream2" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"stream2" org_table:"stream2" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 792 `type:ROW row_event:{table_name:"stream2" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 793 `type:ROW row_event:{table_name:"stream2" row_changes:{after:{lengths:1 lengths:3 values:"2aaa"}}}`, 794 `gtid`, 795 `commit`, 796 }}, 797 }} 798 799 filter := &binlogdatapb.Filter{ 800 Rules: []*binlogdatapb.Rule{{ 801 Match: "stream2", 802 Filter: "select * from stream2", 803 }}, 804 } 805 runCases(t, filter, testcases, "current", nil) 806 } 807 808 func TestStatements(t *testing.T) { 809 if testing.Short() { 810 t.Skip() 811 } 812 813 execStatements(t, []string{ 814 "create table stream1(id int, val varbinary(128), primary key(id))", 815 "create table stream2(id int, val varbinary(128), primary key(id))", 816 }) 817 defer execStatements(t, []string{ 818 "drop table stream1", 819 "drop table stream2", 820 }) 821 engine.se.Reload(context.Background()) 822 823 testcases := []testcase{{ 824 input: []string{ 825 "begin", 826 "insert into stream1 values (1, 'aaa')", 827 "update stream1 set val='bbb' where id = 1", 828 "commit", 829 }, 830 output: [][]string{{ 831 `begin`, 832 `type:FIELD field_event:{table_name:"stream1" fields:{name:"id" type:INT32 table:"stream1" org_table:"stream1" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"stream1" org_table:"stream1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 833 `type:ROW row_event:{table_name:"stream1" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 834 `type:ROW row_event:{table_name:"stream1" row_changes:{before:{lengths:1 lengths:3 values:"1aaa"} after:{lengths:1 lengths:3 values:"1bbb"}}}`, 835 `gtid`, 836 `commit`, 837 }}, 838 }, { 839 // Normal DDL. 840 input: "alter table stream1 change column val val varbinary(128)", 841 output: [][]string{{ 842 `gtid`, 843 `type:DDL statement:"alter table stream1 change column val val varbinary(128)"`, 844 }}, 845 }, { 846 // DDL padded with comments. 847 input: " /* prefix */ alter table stream1 change column val val varbinary(256) /* suffix */ ", 848 output: [][]string{{ 849 `gtid`, 850 `type:DDL statement:"/* prefix */ alter table stream1 change column val val varbinary(256) /* suffix */"`, 851 }}, 852 }, { 853 // Multiple tables, and multiple rows changed per statement. 854 input: []string{ 855 "begin", 856 "insert into stream1 values (2, 'bbb')", 857 "insert into stream2 values (1, 'aaa')", 858 "update stream1 set val='ccc'", 859 "delete from stream1", 860 "commit", 861 }, 862 output: [][]string{{ 863 `begin`, 864 `type:FIELD field_event:{table_name:"stream1" fields:{name:"id" type:INT32 table:"stream1" org_table:"stream1" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"stream1" org_table:"stream1" database:"vttest" org_name:"val" column_length:256 charset:63 column_type:"varbinary(256)"}}`, 865 `type:ROW row_event:{table_name:"stream1" row_changes:{after:{lengths:1 lengths:3 values:"2bbb"}}}`, 866 `type:FIELD field_event:{table_name:"stream2" fields:{name:"id" type:INT32 table:"stream2" org_table:"stream2" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"stream2" org_table:"stream2" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 867 `type:ROW row_event:{table_name:"stream2" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 868 `type:ROW row_event:{table_name:"stream1" ` + 869 `row_changes:{before:{lengths:1 lengths:3 values:"1bbb"} after:{lengths:1 lengths:3 values:"1ccc"}} ` + 870 `row_changes:{before:{lengths:1 lengths:3 values:"2bbb"} after:{lengths:1 lengths:3 values:"2ccc"}}}`, 871 `type:ROW row_event:{table_name:"stream1" ` + 872 `row_changes:{before:{lengths:1 lengths:3 values:"1ccc"}} ` + 873 `row_changes:{before:{lengths:1 lengths:3 values:"2ccc"}}}`, 874 `gtid`, 875 `commit`, 876 }}, 877 }, { 878 // truncate is a DDL 879 input: "truncate table stream2", 880 output: [][]string{{ 881 `gtid`, 882 `type:DDL statement:"truncate table stream2"`, 883 }}, 884 }, { 885 // Reverse alter table, else FilePos tests fail 886 input: " /* prefix */ alter table stream1 change column val val varbinary(128) /* suffix */ ", 887 output: [][]string{{ 888 `gtid`, 889 `type:DDL statement:"/* prefix */ alter table stream1 change column val val varbinary(128) /* suffix */"`, 890 }}, 891 }} 892 runCases(t, nil, testcases, "current", nil) 893 // Test FilePos flavor 894 savedEngine := engine 895 defer func() { engine = savedEngine }() 896 engine = customEngine(t, func(in mysql.ConnParams) mysql.ConnParams { 897 in.Flavor = "FilePos" 898 return in 899 }) 900 901 defer engine.Close() 902 runCases(t, nil, testcases, "current", nil) 903 } 904 905 // TestOther tests "other" and "priv" statements. These statements can 906 // produce very different events depending on the version of mysql or 907 // mariadb. So, we just show that vreplication transmits "OTHER" events 908 // if the binlog is affected by the statement. 909 func TestOther(t *testing.T) { 910 if testing.Short() { 911 t.Skip() 912 } 913 914 execStatements(t, []string{ 915 "create table stream1(id int, val varbinary(128), primary key(id))", 916 "create table stream2(id int, val varbinary(128), primary key(id))", 917 }) 918 defer execStatements(t, []string{ 919 "drop table stream1", 920 "drop table stream2", 921 }) 922 engine.se.Reload(context.Background()) 923 924 testcases := []string{ 925 "repair table stream2", 926 "optimize table stream2", 927 "analyze table stream2", 928 "select * from stream1", 929 "set @val=1", 930 "show tables", 931 "describe stream1", 932 "grant select on stream1 to current_user()", 933 "revoke select on stream1 from current_user()", 934 } 935 936 // customRun is a modified version of runCases. 937 customRun := func(mode string) { 938 t.Logf("Run mode: %v", mode) 939 ctx, cancel := context.WithCancel(context.Background()) 940 defer cancel() 941 wg, ch := startStream(ctx, t, nil, "", nil) 942 defer wg.Wait() 943 want := [][]string{{ 944 `gtid`, 945 `type:OTHER`, 946 }} 947 948 for _, stmt := range testcases { 949 startPosition := primaryPosition(t) 950 execStatement(t, stmt) 951 endPosition := primaryPosition(t) 952 if startPosition == endPosition { 953 t.Logf("statement %s did not affect binlog", stmt) 954 continue 955 } 956 expectLog(ctx, t, stmt, ch, want) 957 } 958 cancel() 959 if evs, ok := <-ch; ok { 960 t.Fatalf("unexpected evs: %v", evs) 961 } 962 } 963 customRun("gtid") 964 965 // Test FilePos flavor 966 savedEngine := engine 967 defer func() { engine = savedEngine }() 968 engine = customEngine(t, func(in mysql.ConnParams) mysql.ConnParams { 969 in.Flavor = "FilePos" 970 return in 971 }) 972 defer engine.Close() 973 customRun("filePos") 974 } 975 976 func TestRegexp(t *testing.T) { 977 if testing.Short() { 978 t.Skip() 979 } 980 981 execStatements(t, []string{ 982 "create table yes_stream(id int, val varbinary(128), primary key(id))", 983 "create table no_stream(id int, val varbinary(128), primary key(id))", 984 }) 985 defer execStatements(t, []string{ 986 "drop table yes_stream", 987 "drop table no_stream", 988 }) 989 engine.se.Reload(context.Background()) 990 991 filter := &binlogdatapb.Filter{ 992 Rules: []*binlogdatapb.Rule{{ 993 Match: "/yes.*/", 994 }}, 995 } 996 997 testcases := []testcase{{ 998 input: []string{ 999 "begin", 1000 "insert into yes_stream values (1, 'aaa')", 1001 "insert into no_stream values (2, 'bbb')", 1002 "update yes_stream set val='bbb' where id = 1", 1003 "update no_stream set val='bbb' where id = 2", 1004 "commit", 1005 }, 1006 output: [][]string{{ 1007 `begin`, 1008 `type:FIELD field_event:{table_name:"yes_stream" fields:{name:"id" type:INT32 table:"yes_stream" org_table:"yes_stream" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"yes_stream" org_table:"yes_stream" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1009 `type:ROW row_event:{table_name:"yes_stream" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 1010 `type:ROW row_event:{table_name:"yes_stream" row_changes:{before:{lengths:1 lengths:3 values:"1aaa"} after:{lengths:1 lengths:3 values:"1bbb"}}}`, 1011 `gtid`, 1012 `commit`, 1013 }}, 1014 }} 1015 runCases(t, filter, testcases, "", nil) 1016 } 1017 1018 func TestREKeyRange(t *testing.T) { 1019 if testing.Short() { 1020 t.Skip() 1021 } 1022 ignoreKeyspaceShardInFieldAndRowEvents = false 1023 defer func() { 1024 ignoreKeyspaceShardInFieldAndRowEvents = true 1025 }() 1026 // Needed for this test to run if run standalone 1027 engine.watcherOnce.Do(engine.setWatch) 1028 1029 execStatements(t, []string{ 1030 "create table t1(id1 int, id2 int, val varbinary(128), primary key(id1))", 1031 }) 1032 defer execStatements(t, []string{ 1033 "drop table t1", 1034 }) 1035 engine.se.Reload(context.Background()) 1036 1037 setVSchema(t, shardedVSchema) 1038 defer env.SetVSchema("{}") 1039 1040 ctx, cancel := context.WithCancel(context.Background()) 1041 defer cancel() 1042 1043 filter := &binlogdatapb.Filter{ 1044 Rules: []*binlogdatapb.Rule{{ 1045 Match: "/.*/", 1046 Filter: "-80", 1047 }}, 1048 } 1049 wg, ch := startStream(ctx, t, filter, "", nil) 1050 defer wg.Wait() 1051 // 1, 2, 3 and 5 are in shard -80. 1052 // 4 and 6 are in shard 80-. 1053 input := []string{ 1054 "begin", 1055 "insert into t1 values (1, 4, 'aaa')", 1056 "insert into t1 values (4, 1, 'bbb')", 1057 // Stay in shard. 1058 "update t1 set id1 = 2 where id1 = 1", 1059 // Move from -80 to 80-. 1060 "update t1 set id1 = 6 where id1 = 2", 1061 // Move from 80- to -80. 1062 "update t1 set id1 = 3 where id1 = 4", 1063 "commit", 1064 } 1065 execStatements(t, input) 1066 expectLog(ctx, t, input, ch, [][]string{{ 1067 `begin`, 1068 `type:FIELD field_event:{table_name:"t1" fields:{name:"id1" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id1" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"id2" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id2" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"} keyspace:"vttest" shard:"0"}`, 1069 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:1 lengths:3 values:"14aaa"}} keyspace:"vttest" shard:"0"}`, 1070 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:1 lengths:3 values:"14aaa"} after:{lengths:1 lengths:1 lengths:3 values:"24aaa"}} keyspace:"vttest" shard:"0"}`, 1071 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:1 lengths:3 values:"24aaa"}} keyspace:"vttest" shard:"0"}`, 1072 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:1 lengths:3 values:"31bbb"}} keyspace:"vttest" shard:"0"}`, 1073 `gtid`, 1074 `commit`, 1075 }}) 1076 1077 // Switch the vschema to make id2 the primary vindex. 1078 altVSchema := `{ 1079 "sharded": true, 1080 "vindexes": { 1081 "hash": { 1082 "type": "hash" 1083 } 1084 }, 1085 "tables": { 1086 "t1": { 1087 "column_vindexes": [ 1088 { 1089 "column": "id2", 1090 "name": "hash" 1091 } 1092 ] 1093 } 1094 } 1095 }` 1096 setVSchema(t, altVSchema) 1097 1098 // Only the first insert should be sent. 1099 input = []string{ 1100 "begin", 1101 "insert into t1 values (4, 1, 'aaa')", 1102 "insert into t1 values (1, 4, 'aaa')", 1103 "commit", 1104 } 1105 execStatements(t, input) 1106 expectLog(ctx, t, input, ch, [][]string{{ 1107 `begin`, 1108 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:1 lengths:3 values:"41aaa"}} keyspace:"vttest" shard:"0"}`, 1109 `gtid`, 1110 `commit`, 1111 }}) 1112 cancel() 1113 } 1114 1115 func TestInKeyRangeMultiColumn(t *testing.T) { 1116 if testing.Short() { 1117 t.Skip() 1118 } 1119 engine.watcherOnce.Do(engine.setWatch) 1120 engine.se.Reload(context.Background()) 1121 1122 execStatements(t, []string{ 1123 "create table t1(region int, id int, val varbinary(128), primary key(id))", 1124 }) 1125 defer execStatements(t, []string{ 1126 "drop table t1", 1127 }) 1128 engine.se.Reload(context.Background()) 1129 1130 setVSchema(t, multicolumnVSchema) 1131 defer env.SetVSchema("{}") 1132 1133 ctx, cancel := context.WithCancel(context.Background()) 1134 defer cancel() 1135 1136 filter := &binlogdatapb.Filter{ 1137 Rules: []*binlogdatapb.Rule{{ 1138 Match: "t1", 1139 Filter: "select id, region, val, keyspace_id() from t1 where in_keyrange('-80')", 1140 }}, 1141 } 1142 wg, ch := startStream(ctx, t, filter, "", nil) 1143 defer wg.Wait() 1144 1145 // 1, 2, 3 and 5 are in shard -80. 1146 // 4 and 6 are in shard 80-. 1147 input := []string{ 1148 "begin", 1149 "insert into t1 values (1, 1, 'aaa')", 1150 "insert into t1 values (128, 2, 'bbb')", 1151 // Stay in shard. 1152 "update t1 set region = 2 where id = 1", 1153 // Move from -80 to 80-. 1154 "update t1 set region = 128 where id = 1", 1155 // Move from 80- to -80. 1156 "update t1 set region = 1 where id = 2", 1157 "commit", 1158 } 1159 execStatements(t, input) 1160 expectLog(ctx, t, input, ch, [][]string{{ 1161 `begin`, 1162 `type:FIELD field_event:{table_name:"t1" fields:{name:"id" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"region" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"region" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"} fields:{name:"keyspace_id" type:VARBINARY}}`, 1163 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:1 lengths:3 lengths:9 values:"11aaa\x01\x16k@\xb4J\xbaK\xd6"}}}`, 1164 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:1 lengths:3 lengths:9 values:"11aaa\x01\x16k@\xb4J\xbaK\xd6"} ` + 1165 `after:{lengths:1 lengths:1 lengths:3 lengths:9 values:"12aaa\x02\x16k@\xb4J\xbaK\xd6"}}}`, 1166 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:1 lengths:3 lengths:9 values:"12aaa\x02\x16k@\xb4J\xbaK\xd6"}}}`, 1167 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:1 lengths:3 lengths:9 values:"21bbb\x01\x06\xe7\xea\"Î’p\x8f"}}}`, 1168 `gtid`, 1169 `commit`, 1170 }}) 1171 cancel() 1172 } 1173 1174 func TestREMultiColumnVindex(t *testing.T) { 1175 if testing.Short() { 1176 t.Skip() 1177 } 1178 engine.watcherOnce.Do(engine.setWatch) 1179 1180 execStatements(t, []string{ 1181 "create table t1(region int, id int, val varbinary(128), primary key(id))", 1182 }) 1183 defer execStatements(t, []string{ 1184 "drop table t1", 1185 }) 1186 engine.se.Reload(context.Background()) 1187 1188 setVSchema(t, multicolumnVSchema) 1189 defer env.SetVSchema("{}") 1190 1191 ctx, cancel := context.WithCancel(context.Background()) 1192 defer cancel() 1193 1194 filter := &binlogdatapb.Filter{ 1195 Rules: []*binlogdatapb.Rule{{ 1196 Match: "/.*/", 1197 Filter: "-80", 1198 }}, 1199 } 1200 wg, ch := startStream(ctx, t, filter, "", nil) 1201 defer wg.Wait() 1202 1203 // 1, 2, 3 and 5 are in shard -80. 1204 // 4 and 6 are in shard 80-. 1205 input := []string{ 1206 "begin", 1207 "insert into t1 values (1, 1, 'aaa')", 1208 "insert into t1 values (128, 2, 'bbb')", 1209 // Stay in shard. 1210 "update t1 set region = 2 where id = 1", 1211 // Move from -80 to 80-. 1212 "update t1 set region = 128 where id = 1", 1213 // Move from 80- to -80. 1214 "update t1 set region = 1 where id = 2", 1215 "commit", 1216 } 1217 execStatements(t, input) 1218 expectLog(ctx, t, input, ch, [][]string{{ 1219 `begin`, 1220 `type:FIELD field_event:{table_name:"t1" fields:{name:"region" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"region" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"id" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1221 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:1 lengths:3 values:"11aaa"}}}`, 1222 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:1 lengths:3 values:"11aaa"} after:{lengths:1 lengths:1 lengths:3 values:"21aaa"}}}`, 1223 `type:ROW row_event:{table_name:"t1" row_changes:{before:{lengths:1 lengths:1 lengths:3 values:"21aaa"}}}`, 1224 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:1 lengths:3 values:"12bbb"}}}`, 1225 `gtid`, 1226 `commit`, 1227 }}) 1228 cancel() 1229 } 1230 1231 func TestSelectFilter(t *testing.T) { 1232 if testing.Short() { 1233 t.Skip() 1234 } 1235 engine.se.Reload(context.Background()) 1236 1237 execStatements(t, []string{ 1238 "create table t1(id1 int, id2 int, val varbinary(128), primary key(id1))", 1239 }) 1240 defer execStatements(t, []string{ 1241 "drop table t1", 1242 }) 1243 engine.se.Reload(context.Background()) 1244 1245 filter := &binlogdatapb.Filter{ 1246 Rules: []*binlogdatapb.Rule{{ 1247 Match: "t1", 1248 Filter: "select id2, val from t1 where in_keyrange(id2, 'hash', '-80')", 1249 }}, 1250 } 1251 1252 testcases := []testcase{{ 1253 input: []string{ 1254 "begin", 1255 "insert into t1 values (4, 1, 'aaa')", 1256 "insert into t1 values (2, 4, 'aaa')", 1257 "commit", 1258 }, 1259 output: [][]string{{ 1260 `begin`, 1261 `type:FIELD field_event:{table_name:"t1" fields:{name:"id2" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id2" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1262 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 1263 `gtid`, 1264 `commit`, 1265 }}, 1266 }} 1267 runCases(t, filter, testcases, "", nil) 1268 } 1269 1270 func TestDDLAddColumn(t *testing.T) { 1271 if testing.Short() { 1272 t.Skip() 1273 } 1274 1275 execStatements(t, []string{ 1276 "create table ddl_test1(id int, val1 varbinary(128), primary key(id))", 1277 "create table ddl_test2(id int, val1 varbinary(128), primary key(id))", 1278 }) 1279 defer execStatements(t, []string{ 1280 "drop table ddl_test1", 1281 "drop table ddl_test2", 1282 }) 1283 1284 // Record position before the next few statements. 1285 pos := primaryPosition(t) 1286 execStatements(t, []string{ 1287 "begin", 1288 "insert into ddl_test1 values(1, 'aaa')", 1289 "insert into ddl_test2 values(1, 'aaa')", 1290 "commit", 1291 // Adding columns is allowed. 1292 "alter table ddl_test1 add column val2 varbinary(128)", 1293 "alter table ddl_test2 add column val2 varbinary(128)", 1294 "begin", 1295 "insert into ddl_test1 values(2, 'bbb', 'ccc')", 1296 "insert into ddl_test2 values(2, 'bbb', 'ccc')", 1297 "commit", 1298 }) 1299 engine.se.Reload(context.Background()) 1300 1301 ctx, cancel := context.WithCancel(context.Background()) 1302 defer cancel() 1303 1304 // Test RE as well as select-based filters. 1305 filter := &binlogdatapb.Filter{ 1306 Rules: []*binlogdatapb.Rule{{ 1307 Match: "ddl_test2", 1308 Filter: "select * from ddl_test2", 1309 }, { 1310 Match: "/.*/", 1311 }}, 1312 } 1313 1314 ch := make(chan []*binlogdatapb.VEvent) 1315 go func() { 1316 defer close(ch) 1317 if err := vstream(ctx, t, pos, nil, filter, ch); err != nil { 1318 t.Error(err) 1319 } 1320 }() 1321 expectLog(ctx, t, "ddls", ch, [][]string{{ 1322 // Current schema has 3 columns, but they'll be truncated to match the two columns in the event. 1323 `begin`, 1324 `type:FIELD field_event:{table_name:"ddl_test1" fields:{name:"id" type:INT32 table:"ddl_test1" org_table:"ddl_test1" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val1" type:VARBINARY table:"ddl_test1" org_table:"ddl_test1" database:"vttest" org_name:"val1" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1325 `type:ROW row_event:{table_name:"ddl_test1" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 1326 `type:FIELD field_event:{table_name:"ddl_test2" fields:{name:"id" type:INT32 table:"ddl_test2" org_table:"ddl_test2" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val1" type:VARBINARY table:"ddl_test2" org_table:"ddl_test2" database:"vttest" org_name:"val1" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1327 `type:ROW row_event:{table_name:"ddl_test2" row_changes:{after:{lengths:1 lengths:3 values:"1aaa"}}}`, 1328 `gtid`, 1329 `commit`, 1330 }, { 1331 `gtid`, 1332 `type:DDL statement:"alter table ddl_test1 add column val2 varbinary(128)"`, 1333 }, { 1334 `gtid`, 1335 `type:DDL statement:"alter table ddl_test2 add column val2 varbinary(128)"`, 1336 }, { 1337 // The plan will be updated to now include the third column 1338 // because the new table map will have three columns. 1339 `begin`, 1340 `type:FIELD field_event:{table_name:"ddl_test1" fields:{name:"id" type:INT32 table:"ddl_test1" org_table:"ddl_test1" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val1" type:VARBINARY table:"ddl_test1" org_table:"ddl_test1" database:"vttest" org_name:"val1" column_length:128 charset:63 column_type:"varbinary(128)"} fields:{name:"val2" type:VARBINARY table:"ddl_test1" org_table:"ddl_test1" database:"vttest" org_name:"val2" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1341 `type:ROW row_event:{table_name:"ddl_test1" row_changes:{after:{lengths:1 lengths:3 lengths:3 values:"2bbbccc"}}}`, 1342 `type:FIELD field_event:{table_name:"ddl_test2" fields:{name:"id" type:INT32 table:"ddl_test2" org_table:"ddl_test2" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val1" type:VARBINARY table:"ddl_test2" org_table:"ddl_test2" database:"vttest" org_name:"val1" column_length:128 charset:63 column_type:"varbinary(128)"} fields:{name:"val2" type:VARBINARY table:"ddl_test2" org_table:"ddl_test2" database:"vttest" org_name:"val2" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1343 `type:ROW row_event:{table_name:"ddl_test2" row_changes:{after:{lengths:1 lengths:3 lengths:3 values:"2bbbccc"}}}`, 1344 `gtid`, 1345 `commit`, 1346 }}) 1347 } 1348 1349 func TestDDLDropColumn(t *testing.T) { 1350 if testing.Short() { 1351 t.Skip() 1352 } 1353 env.SchemaEngine.Reload(context.Background()) 1354 execStatement(t, "create table ddl_test2(id int, val1 varbinary(128), val2 varbinary(128), primary key(id))") 1355 defer execStatement(t, "drop table ddl_test2") 1356 1357 // Record position before the next few statements. 1358 pos := primaryPosition(t) 1359 execStatements(t, []string{ 1360 "insert into ddl_test2 values(1, 'aaa', 'ccc')", 1361 // Adding columns is allowed. 1362 "alter table ddl_test2 drop column val2", 1363 "insert into ddl_test2 values(2, 'bbb')", 1364 }) 1365 engine.se.Reload(context.Background()) 1366 env.SchemaEngine.Reload(context.Background()) 1367 1368 ctx, cancel := context.WithCancel(context.Background()) 1369 defer cancel() 1370 1371 ch := make(chan []*binlogdatapb.VEvent) 1372 go func() { 1373 for range ch { 1374 } 1375 }() 1376 defer close(ch) 1377 err := vstream(ctx, t, pos, nil, nil, ch) 1378 want := "cannot determine table columns" 1379 if err == nil || !strings.Contains(err.Error(), want) { 1380 t.Errorf("err: %v, must contain %s", err, want) 1381 } 1382 } 1383 1384 func TestUnsentDDL(t *testing.T) { 1385 if testing.Short() { 1386 t.Skip() 1387 } 1388 1389 execStatement(t, "create table unsent(id int, val varbinary(128), primary key(id))") 1390 1391 testcases := []testcase{{ 1392 input: []string{ 1393 "drop table unsent", 1394 }, 1395 // An unsent DDL is sent as an empty transaction. 1396 output: [][]string{{ 1397 `gtid`, 1398 `type:OTHER`, 1399 }}, 1400 }} 1401 1402 filter := &binlogdatapb.Filter{ 1403 Rules: []*binlogdatapb.Rule{{ 1404 Match: "/none/", 1405 }}, 1406 } 1407 runCases(t, filter, testcases, "", nil) 1408 } 1409 1410 func TestBuffering(t *testing.T) { 1411 if testing.Short() { 1412 t.Skip() 1413 } 1414 1415 reset := AdjustPacketSize(10) 1416 defer reset() 1417 1418 execStatement(t, "create table packet_test(id int, val varbinary(128), primary key(id))") 1419 defer execStatement(t, "drop table packet_test") 1420 engine.se.Reload(context.Background()) 1421 1422 testcases := []testcase{{ 1423 // All rows in one packet. 1424 input: []string{ 1425 "begin", 1426 "insert into packet_test values (1, '123')", 1427 "insert into packet_test values (2, '456')", 1428 "commit", 1429 }, 1430 output: [][]string{{ 1431 `begin`, 1432 `type:FIELD field_event:{table_name:"packet_test" fields:{name:"id" type:INT32 table:"packet_test" org_table:"packet_test" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"packet_test" org_table:"packet_test" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1433 `type:ROW row_event:{table_name:"packet_test" row_changes:{after:{lengths:1 lengths:3 values:"1123"}}}`, 1434 `type:ROW row_event:{table_name:"packet_test" row_changes:{after:{lengths:1 lengths:3 values:"2456"}}}`, 1435 `gtid`, 1436 `commit`, 1437 }}, 1438 }, { 1439 // A new row causes packet size to be exceeded. 1440 // Also test deletes 1441 input: []string{ 1442 "begin", 1443 "insert into packet_test values (3, '123456')", 1444 "insert into packet_test values (4, '789012')", 1445 "delete from packet_test where id=3", 1446 "delete from packet_test where id=4", 1447 "commit", 1448 }, 1449 output: [][]string{{ 1450 `begin`, 1451 `type:ROW row_event:{table_name:"packet_test" row_changes:{after:{lengths:1 lengths:6 values:"3123456"}}}`, 1452 }, { 1453 `type:ROW row_event:{table_name:"packet_test" row_changes:{after:{lengths:1 lengths:6 values:"4789012"}}}`, 1454 }, { 1455 `type:ROW row_event:{table_name:"packet_test" row_changes:{before:{lengths:1 lengths:6 values:"3123456"}}}`, 1456 }, { 1457 `type:ROW row_event:{table_name:"packet_test" row_changes:{before:{lengths:1 lengths:6 values:"4789012"}}}`, 1458 `gtid`, 1459 `commit`, 1460 }}, 1461 }, { 1462 // A single row is itself bigger than the packet size. 1463 input: []string{ 1464 "begin", 1465 "insert into packet_test values (5, '123456')", 1466 "insert into packet_test values (6, '12345678901')", 1467 "insert into packet_test values (7, '23456')", 1468 "commit", 1469 }, 1470 output: [][]string{{ 1471 `begin`, 1472 `type:ROW row_event:{table_name:"packet_test" row_changes:{after:{lengths:1 lengths:6 values:"5123456"}}}`, 1473 }, { 1474 `type:ROW row_event:{table_name:"packet_test" row_changes:{after:{lengths:1 lengths:11 values:"612345678901"}}}`, 1475 }, { 1476 `type:ROW row_event:{table_name:"packet_test" row_changes:{after:{lengths:1 lengths:5 values:"723456"}}}`, 1477 `gtid`, 1478 `commit`, 1479 }}, 1480 }, { 1481 // An update packet is bigger because it has a before and after image. 1482 input: []string{ 1483 "begin", 1484 "insert into packet_test values (8, '123')", 1485 "update packet_test set val='456' where id=8", 1486 "commit", 1487 }, 1488 output: [][]string{{ 1489 `begin`, 1490 `type:ROW row_event:{table_name:"packet_test" row_changes:{after:{lengths:1 lengths:3 values:"8123"}}}`, 1491 }, { 1492 `type:ROW row_event:{table_name:"packet_test" row_changes:{before:{lengths:1 lengths:3 values:"8123"} after:{lengths:1 lengths:3 values:"8456"}}}`, 1493 `gtid`, 1494 `commit`, 1495 }}, 1496 }, { 1497 // DDL is in its own packet 1498 input: []string{ 1499 "alter table packet_test change val val varchar(128)", 1500 }, 1501 output: [][]string{{ 1502 `gtid`, 1503 `type:DDL statement:"alter table packet_test change val val varchar(128)"`, 1504 }}, 1505 }} 1506 runCases(t, nil, testcases, "", nil) 1507 } 1508 1509 func TestBestEffortNameInFieldEvent(t *testing.T) { 1510 if testing.Short() { 1511 t.Skip() 1512 } 1513 filter := &binlogdatapb.Filter{ 1514 FieldEventMode: binlogdatapb.Filter_BEST_EFFORT, 1515 Rules: []*binlogdatapb.Rule{{ 1516 Match: "/.*/", 1517 }}, 1518 } 1519 // Modeled after vttablet endtoend compatibility tests. 1520 execStatements(t, []string{ 1521 "create table vitess_test(id int, val varbinary(128), primary key(id))", 1522 }) 1523 position := primaryPosition(t) 1524 execStatements(t, []string{ 1525 "insert into vitess_test values(1, 'abc')", 1526 "rename table vitess_test to vitess_test_new", 1527 }) 1528 1529 defer execStatements(t, []string{ 1530 "drop table vitess_test_new", 1531 }) 1532 engine.se.Reload(context.Background()) 1533 testcases := []testcase{{ 1534 input: []string{ 1535 "insert into vitess_test_new values(2, 'abc')", 1536 }, 1537 // In this case, we don't have information about vitess_test since it was renamed to vitess_test_test. 1538 // information returned by binlog for val column == varchar (rather than varbinary). 1539 output: [][]string{{ 1540 `begin`, 1541 `type:FIELD field_event:{table_name:"vitess_test" fields:{name:"@1" type:INT32} fields:{name:"@2" type:VARCHAR}}`, 1542 `type:ROW row_event:{table_name:"vitess_test" row_changes:{after:{lengths:1 lengths:3 values:"1abc"}}}`, 1543 `gtid`, 1544 `commit`, 1545 }, { 1546 `gtid`, 1547 `type:DDL statement:"rename table vitess_test to vitess_test_new"`, 1548 }, { 1549 `begin`, 1550 `type:FIELD field_event:{table_name:"vitess_test_new" fields:{name:"id" type:INT32 table:"vitess_test_new" org_table:"vitess_test_new" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"vitess_test_new" org_table:"vitess_test_new" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1551 `type:ROW row_event:{table_name:"vitess_test_new" row_changes:{after:{lengths:1 lengths:3 values:"2abc"}}}`, 1552 `gtid`, 1553 `commit`, 1554 }}, 1555 }} 1556 runCases(t, filter, testcases, position, nil) 1557 } 1558 1559 // test that vstreamer ignores tables created by OnlineDDL 1560 func TestInternalTables(t *testing.T) { 1561 if testing.Short() { 1562 t.Skip() 1563 } 1564 filter := &binlogdatapb.Filter{ 1565 FieldEventMode: binlogdatapb.Filter_BEST_EFFORT, 1566 Rules: []*binlogdatapb.Rule{{ 1567 Match: "/.*/", 1568 }}, 1569 } 1570 // Modeled after vttablet endtoend compatibility tests. 1571 execStatements(t, []string{ 1572 "create table vitess_test(id int, val varbinary(128), primary key(id))", 1573 "create table _1e275eef_3b20_11eb_a38f_04ed332e05c2_20201210204529_gho(id int, val varbinary(128), primary key(id))", 1574 "create table _vt_PURGE_1f9194b43b2011eb8a0104ed332e05c2_20201210194431(id int, val varbinary(128), primary key(id))", 1575 "create table _product_old(id int, val varbinary(128), primary key(id))", 1576 }) 1577 position := primaryPosition(t) 1578 execStatements(t, []string{ 1579 "insert into vitess_test values(1, 'abc')", 1580 "insert into _1e275eef_3b20_11eb_a38f_04ed332e05c2_20201210204529_gho values(1, 'abc')", 1581 "insert into _vt_PURGE_1f9194b43b2011eb8a0104ed332e05c2_20201210194431 values(1, 'abc')", 1582 "insert into _product_old values(1, 'abc')", 1583 }) 1584 1585 defer execStatements(t, []string{ 1586 "drop table vitess_test", 1587 "drop table _1e275eef_3b20_11eb_a38f_04ed332e05c2_20201210204529_gho", 1588 "drop table _vt_PURGE_1f9194b43b2011eb8a0104ed332e05c2_20201210194431", 1589 "drop table _product_old", 1590 }) 1591 engine.se.Reload(context.Background()) 1592 testcases := []testcase{{ 1593 input: []string{ 1594 "insert into vitess_test values(2, 'abc')", 1595 }, 1596 // In this case, we don't have information about vitess_test since it was renamed to vitess_test_test. 1597 // information returned by binlog for val column == varchar (rather than varbinary). 1598 output: [][]string{{ 1599 `begin`, 1600 `type:FIELD field_event:{table_name:"vitess_test" fields:{name:"id" type:INT32 table:"vitess_test" org_table:"vitess_test" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"vitess_test" org_table:"vitess_test" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1601 `type:ROW row_event:{table_name:"vitess_test" row_changes:{after:{lengths:1 lengths:3 values:"1abc"}}}`, 1602 `gtid`, 1603 `commit`, 1604 }, {`begin`, `gtid`, `commit`}, {`begin`, `gtid`, `commit`}, {`begin`, `gtid`, `commit`}, // => inserts into the three internal comments 1605 { 1606 `begin`, 1607 `type:ROW row_event:{table_name:"vitess_test" row_changes:{after:{lengths:1 lengths:3 values:"2abc"}}}`, 1608 `gtid`, 1609 `commit`, 1610 }}, 1611 }} 1612 runCases(t, filter, testcases, position, nil) 1613 } 1614 1615 func TestTypes(t *testing.T) { 1616 if testing.Short() { 1617 t.Skip() 1618 } 1619 1620 // Modeled after vttablet endtoend compatibility tests. 1621 execStatements(t, []string{ 1622 "create table vitess_ints(tiny tinyint, tinyu tinyint unsigned, small smallint, smallu smallint unsigned, medium mediumint, mediumu mediumint unsigned, normal int, normalu int unsigned, big bigint, bigu bigint unsigned, y year, primary key(tiny))", 1623 "create table vitess_fracts(id int, deci decimal(5,2), num numeric(5,2), f float, d double, primary key(id))", 1624 "create table vitess_strings(vb varbinary(16), c char(16), vc varchar(16), b binary(4), tb tinyblob, bl blob, ttx tinytext, tx text, en enum('a','b'), s set('a','b'), primary key(vb))", 1625 "create table vitess_misc(id int, b bit(8), d date, dt datetime, t time, g geometry, primary key(id))", 1626 "create table vitess_null(id int, val varbinary(128), primary key(id))", 1627 "create table vitess_decimal(id int, dec1 decimal(12,4), dec2 decimal(13,4), primary key(id))", 1628 }) 1629 defer execStatements(t, []string{ 1630 "drop table vitess_ints", 1631 "drop table vitess_fracts", 1632 "drop table vitess_strings", 1633 "drop table vitess_misc", 1634 "drop table vitess_null", 1635 "drop table vitess_decimal", 1636 }) 1637 engine.se.Reload(context.Background()) 1638 1639 testcases := []testcase{{ 1640 input: []string{ 1641 "insert into vitess_ints values(-128, 255, -32768, 65535, -8388608, 16777215, -2147483648, 4294967295, -9223372036854775808, 18446744073709551615, 2012)", 1642 }, 1643 output: [][]string{{ 1644 `begin`, 1645 `type:FIELD field_event:{table_name:"vitess_ints" fields:{name:"tiny" type:INT8 table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"tiny" column_length:4 charset:63 column_type:"tinyint(4)"} fields:{name:"tinyu" type:UINT8 table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"tinyu" column_length:3 charset:63 column_type:"tinyint(3) unsigned"} fields:{name:"small" type:INT16 table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"small" column_length:6 charset:63 column_type:"smallint(6)"} fields:{name:"smallu" type:UINT16 table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"smallu" column_length:5 charset:63 column_type:"smallint(5) unsigned"} fields:{name:"medium" type:INT24 table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"medium" column_length:9 charset:63 column_type:"mediumint(9)"} fields:{name:"mediumu" type:UINT24 table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"mediumu" column_length:8 charset:63 column_type:"mediumint(8) unsigned"} fields:{name:"normal" type:INT32 table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"normal" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"normalu" type:UINT32 table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"normalu" column_length:10 charset:63 column_type:"int(10) unsigned"} fields:{name:"big" type:INT64 table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"big" column_length:20 charset:63 column_type:"bigint(20)"} fields:{name:"bigu" type:UINT64 table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"bigu" column_length:20 charset:63 column_type:"bigint(20) unsigned"} fields:{name:"y" type:YEAR table:"vitess_ints" org_table:"vitess_ints" database:"vttest" org_name:"y" column_length:4 charset:63 column_type:"year(4)"}}`, 1646 `type:ROW row_event:{table_name:"vitess_ints" row_changes:{after:{lengths:4 lengths:3 lengths:6 lengths:5 lengths:8 lengths:8 lengths:11 lengths:10 lengths:20 lengths:20 lengths:4 values:"` + 1647 `-128` + 1648 `255` + 1649 `-32768` + 1650 `65535` + 1651 `-8388608` + 1652 `16777215` + 1653 `-2147483648` + 1654 `4294967295` + 1655 `-9223372036854775808` + 1656 `18446744073709551615` + 1657 `2012` + 1658 `"}}}`, 1659 `gtid`, 1660 `commit`, 1661 }}, 1662 }, { 1663 input: []string{ 1664 "insert into vitess_fracts values(1, 1.99, 2.99, 3.99, 4.99)", 1665 }, 1666 output: [][]string{{ 1667 `begin`, 1668 `type:FIELD field_event:{table_name:"vitess_fracts" fields:{name:"id" type:INT32 table:"vitess_fracts" org_table:"vitess_fracts" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"deci" type:DECIMAL table:"vitess_fracts" org_table:"vitess_fracts" database:"vttest" org_name:"deci" column_length:7 charset:63 decimals:2 column_type:"decimal(5,2)"} fields:{name:"num" type:DECIMAL table:"vitess_fracts" org_table:"vitess_fracts" database:"vttest" org_name:"num" column_length:7 charset:63 decimals:2 column_type:"decimal(5,2)"} fields:{name:"f" type:FLOAT32 table:"vitess_fracts" org_table:"vitess_fracts" database:"vttest" org_name:"f" column_length:12 charset:63 decimals:31 column_type:"float"} fields:{name:"d" type:FLOAT64 table:"vitess_fracts" org_table:"vitess_fracts" database:"vttest" org_name:"d" column_length:22 charset:63 decimals:31 column_type:"double"}}`, 1669 `type:ROW row_event:{table_name:"vitess_fracts" row_changes:{after:{lengths:1 lengths:4 lengths:4 lengths:8 lengths:8 values:"11.992.993.99E+004.99E+00"}}}`, 1670 `gtid`, 1671 `commit`, 1672 }}, 1673 }, { 1674 // TODO(sougou): validate that binary and char data generate correct DMLs on the other end. 1675 input: []string{ 1676 "insert into vitess_strings values('a', 'b', 'c', 'd\000\000\000', 'e', 'f', 'g', 'h', 'a', 'a,b')", 1677 }, 1678 output: [][]string{{ 1679 `begin`, 1680 `type:FIELD field_event:{table_name:"vitess_strings" fields:{name:"vb" type:VARBINARY table:"vitess_strings" org_table:"vitess_strings" database:"vttest" org_name:"vb" column_length:16 charset:63 column_type:"varbinary(16)"} fields:{name:"c" type:CHAR table:"vitess_strings" org_table:"vitess_strings" database:"vttest" org_name:"c" column_length:64 charset:45 column_type:"char(16)"} fields:{name:"vc" type:VARCHAR table:"vitess_strings" org_table:"vitess_strings" database:"vttest" org_name:"vc" column_length:64 charset:45 column_type:"varchar(16)"} fields:{name:"b" type:BINARY table:"vitess_strings" org_table:"vitess_strings" database:"vttest" org_name:"b" column_length:4 charset:63 column_type:"binary(4)"} fields:{name:"tb" type:BLOB table:"vitess_strings" org_table:"vitess_strings" database:"vttest" org_name:"tb" column_length:255 charset:63 column_type:"tinyblob"} fields:{name:"bl" type:BLOB table:"vitess_strings" org_table:"vitess_strings" database:"vttest" org_name:"bl" column_length:65535 charset:63 column_type:"blob"} fields:{name:"ttx" type:TEXT table:"vitess_strings" org_table:"vitess_strings" database:"vttest" org_name:"ttx" column_length:1020 charset:45 column_type:"tinytext"} fields:{name:"tx" type:TEXT table:"vitess_strings" org_table:"vitess_strings" database:"vttest" org_name:"tx" column_length:262140 charset:45 column_type:"text"} fields:{name:"en" type:ENUM table:"vitess_strings" org_table:"vitess_strings" database:"vttest" org_name:"en" column_length:4 charset:45 column_type:"enum('a','b')"} fields:{name:"s" type:SET table:"vitess_strings" org_table:"vitess_strings" database:"vttest" org_name:"s" column_length:12 charset:45 column_type:"set('a','b')"}}`, 1681 `type:ROW row_event:{table_name:"vitess_strings" row_changes:{after:{lengths:1 lengths:1 lengths:1 lengths:4 lengths:1 lengths:1 lengths:1 lengths:1 lengths:1 lengths:1 ` + 1682 `values:"abcd\x00\x00\x00efgh13"}}}`, 1683 `gtid`, 1684 `commit`, 1685 }}, 1686 }, { 1687 // TODO(sougou): validate that the geometry value generates the correct DMLs on the other end. 1688 input: []string{ 1689 "insert into vitess_misc values(1, '\x01', '2012-01-01', '2012-01-01 15:45:45', '15:45:45', point(1, 2))", 1690 }, 1691 output: [][]string{{ 1692 `begin`, 1693 `type:FIELD field_event:{table_name:"vitess_misc" fields:{name:"id" type:INT32 table:"vitess_misc" org_table:"vitess_misc" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"b" type:BIT table:"vitess_misc" org_table:"vitess_misc" database:"vttest" org_name:"b" column_length:8 charset:63 column_type:"bit(8)"} fields:{name:"d" type:DATE table:"vitess_misc" org_table:"vitess_misc" database:"vttest" org_name:"d" column_length:10 charset:63 column_type:"date"} fields:{name:"dt" type:DATETIME table:"vitess_misc" org_table:"vitess_misc" database:"vttest" org_name:"dt" column_length:19 charset:63 column_type:"datetime"} fields:{name:"t" type:TIME table:"vitess_misc" org_table:"vitess_misc" database:"vttest" org_name:"t" column_length:10 charset:63 column_type:"time"} fields:{name:"g" type:GEOMETRY table:"vitess_misc" org_table:"vitess_misc" database:"vttest" org_name:"g" column_length:4294967295 charset:63 column_type:"geometry"}}`, 1694 `type:ROW row_event:{table_name:"vitess_misc" row_changes:{after:{lengths:1 lengths:1 lengths:10 lengths:19 lengths:8 lengths:25 values:"1\x012012-01-012012-01-01 15:45:4515:45:45\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@"}}}`, 1695 `gtid`, 1696 `commit`, 1697 }}, 1698 }, { 1699 input: []string{ 1700 "insert into vitess_null values(1, null)", 1701 }, 1702 output: [][]string{{ 1703 `begin`, 1704 `type:FIELD field_event:{table_name:"vitess_null" fields:{name:"id" type:INT32 table:"vitess_null" org_table:"vitess_null" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"vitess_null" org_table:"vitess_null" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 1705 `type:ROW row_event:{table_name:"vitess_null" row_changes:{after:{lengths:1 lengths:-1 values:"1"}}}`, 1706 `gtid`, 1707 `commit`, 1708 }}, 1709 }, { 1710 input: []string{ 1711 "insert into vitess_decimal values(1, 1.23, 1.23)", 1712 "insert into vitess_decimal values(2, -1.23, -1.23)", 1713 "insert into vitess_decimal values(3, 0000000001.23, 0000000001.23)", 1714 "insert into vitess_decimal values(4, -0000000001.23, -0000000001.23)", 1715 }, 1716 output: [][]string{{ 1717 `begin`, 1718 `type:FIELD field_event:{table_name:"vitess_decimal" fields:{name:"id" type:INT32 table:"vitess_decimal" org_table:"vitess_decimal" database:"vttest" org_name:"id" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"dec1" type:DECIMAL table:"vitess_decimal" org_table:"vitess_decimal" database:"vttest" org_name:"dec1" column_length:14 charset:63 decimals:4 column_type:"decimal(12,4)"} fields:{name:"dec2" type:DECIMAL table:"vitess_decimal" org_table:"vitess_decimal" database:"vttest" org_name:"dec2" column_length:15 charset:63 decimals:4 column_type:"decimal(13,4)"}}`, 1719 `type:ROW row_event:{table_name:"vitess_decimal" row_changes:{after:{lengths:1 lengths:6 lengths:6 values:"11.23001.2300"}}}`, 1720 `gtid`, 1721 `commit`, 1722 }, { 1723 `begin`, 1724 `type:ROW row_event:{table_name:"vitess_decimal" row_changes:{after:{lengths:1 lengths:7 lengths:7 values:"2-1.2300-1.2300"}}}`, 1725 `gtid`, 1726 `commit`, 1727 }, { 1728 `begin`, 1729 `type:ROW row_event:{table_name:"vitess_decimal" row_changes:{after:{lengths:1 lengths:6 lengths:6 values:"31.23001.2300"}}}`, 1730 `gtid`, 1731 `commit`, 1732 }, { 1733 `begin`, 1734 `type:ROW row_event:{table_name:"vitess_decimal" row_changes:{after:{lengths:1 lengths:7 lengths:7 values:"4-1.2300-1.2300"}}}`, 1735 `gtid`, 1736 `commit`, 1737 }}, 1738 }} 1739 runCases(t, nil, testcases, "", nil) 1740 } 1741 1742 func TestJSON(t *testing.T) { 1743 log.Errorf("TestJSON: flavor is %s", env.Flavor) 1744 // JSON is supported only after mysql57. 1745 if !strings.Contains(env.Flavor, "mysql57") { 1746 return 1747 } 1748 if err := env.Mysqld.ExecuteSuperQuery(context.Background(), "create table vitess_json(id int default 1, val json, primary key(id))"); err != nil { 1749 // If it's a syntax error, MySQL is an older version. Skip this test. 1750 if strings.Contains(err.Error(), "syntax") { 1751 return 1752 } 1753 t.Fatal(err) 1754 } 1755 defer execStatement(t, "drop table vitess_json") 1756 engine.se.Reload(context.Background()) 1757 jsonValues := []string{"{}", "123456", `"vtTablet"`, `{"foo":"bar"}`, `["abc",3.14,true]`} 1758 1759 var inputs, outputs []string 1760 var outputsArray [][]string 1761 fieldAdded := false 1762 var expect = func(in string) string { 1763 return strings.ReplaceAll(in, "\"", "\\\"") 1764 } 1765 for i, val := range jsonValues { 1766 inputs = append(inputs, fmt.Sprintf("insert into vitess_json values(%d, %s)", i+1, encodeString(val))) 1767 1768 outputs = []string{} 1769 outputs = append(outputs, `begin`) 1770 if !fieldAdded { 1771 outputs = append(outputs, `type:FIELD field_event:{table_name:"vitess_json" fields:{name:"id" type:INT32 table:"vitess_json" org_table:"vitess_json" database:"vttest" org_name:"id" column_length:11 charset:63} fields:{name:"val" type:JSON table:"vitess_json" org_table:"vitess_json" database:"vttest" org_name:"val" column_length:4294967295 charset:63}}`) 1772 fieldAdded = true 1773 } 1774 out := expect(val) 1775 1776 outputs = append(outputs, fmt.Sprintf(`type:ROW row_event:{table_name:"vitess_json" row_changes:{after:{lengths:1 lengths:%d values:"%d%s"}}}`, 1777 len(val), i+1 /*id increments*/, out)) 1778 outputs = append(outputs, `gtid`) 1779 outputs = append(outputs, `commit`) 1780 outputsArray = append(outputsArray, outputs) 1781 } 1782 testcases := []testcase{{ 1783 input: inputs, 1784 output: outputsArray, 1785 }} 1786 runCases(t, nil, testcases, "", nil) 1787 } 1788 1789 func TestExternalTable(t *testing.T) { 1790 if testing.Short() { 1791 t.Skip() 1792 } 1793 1794 execStatements(t, []string{ 1795 "create database external", 1796 "create table external.ext(id int, val varbinary(128), primary key(id))", 1797 }) 1798 defer execStatements(t, []string{ 1799 "drop database external", 1800 }) 1801 engine.se.Reload(context.Background()) 1802 1803 testcases := []testcase{{ 1804 input: []string{ 1805 "begin", 1806 "insert into external.ext values (1, 'aaa')", 1807 "commit", 1808 }, 1809 // External table events don't get sent. 1810 output: [][]string{{ 1811 `begin`, 1812 `gtid`, 1813 `commit`, 1814 }}, 1815 }} 1816 runCases(t, nil, testcases, "", nil) 1817 } 1818 1819 func TestJournal(t *testing.T) { 1820 if testing.Short() { 1821 t.Skip() 1822 } 1823 1824 execStatements(t, []string{ 1825 "create table if not exists _vt.resharding_journal(id int, db_name varchar(128), val blob, primary key(id))", 1826 }) 1827 defer execStatements(t, []string{ 1828 "drop table _vt.resharding_journal", 1829 }) 1830 engine.se.Reload(context.Background()) 1831 1832 journal1 := &binlogdatapb.Journal{ 1833 Id: 1, 1834 MigrationType: binlogdatapb.MigrationType_SHARDS, 1835 } 1836 journal2 := &binlogdatapb.Journal{ 1837 Id: 2, 1838 MigrationType: binlogdatapb.MigrationType_SHARDS, 1839 } 1840 testcases := []testcase{{ 1841 input: []string{ 1842 "begin", 1843 fmt.Sprintf("insert into _vt.resharding_journal values(1, 'vttest', '%v')", journal1.String()), 1844 fmt.Sprintf("insert into _vt.resharding_journal values(2, 'nosend', '%v')", journal2.String()), 1845 "commit", 1846 }, 1847 // External table events don't get sent. 1848 output: [][]string{{ 1849 `begin`, 1850 `type:JOURNAL journal:{id:1 migration_type:SHARDS}`, 1851 `gtid`, 1852 `commit`, 1853 }}, 1854 }} 1855 runCases(t, nil, testcases, "", nil) 1856 } 1857 1858 func TestMinimalMode(t *testing.T) { 1859 if testing.Short() { 1860 t.Skip() 1861 } 1862 1863 execStatements(t, []string{ 1864 "create table t1(id int, val1 varbinary(128), val2 varbinary(128), primary key(id))", 1865 "insert into t1 values(1, 'aaa', 'bbb')", 1866 }) 1867 defer execStatements(t, []string{ 1868 "drop table t1", 1869 }) 1870 engine.se.Reload(context.Background()) 1871 1872 // Record position before the next few statements. 1873 pos := primaryPosition(t) 1874 execStatements(t, []string{ 1875 "set @@session.binlog_row_image='minimal'", 1876 "update t1 set val1='bbb' where id=1", 1877 "set @@session.binlog_row_image='full'", 1878 }) 1879 1880 ctx, cancel := context.WithCancel(context.Background()) 1881 defer cancel() 1882 1883 ch := make(chan []*binlogdatapb.VEvent) 1884 go func() { 1885 for evs := range ch { 1886 t.Errorf("received: %v", evs) 1887 } 1888 }() 1889 defer close(ch) 1890 err := vstream(ctx, t, pos, nil, nil, ch) 1891 want := "partial row image encountered" 1892 if err == nil || !strings.Contains(err.Error(), want) { 1893 t.Errorf("err: %v, must contain '%s'", err, want) 1894 } 1895 } 1896 1897 func TestStatementMode(t *testing.T) { 1898 if testing.Short() { 1899 t.Skip() 1900 } 1901 execStatements(t, []string{ 1902 "create table stream1(id int, val varbinary(128), primary key(id))", 1903 "create table stream2(id int, val varbinary(128), primary key(id))", 1904 }) 1905 1906 engine.se.Reload(context.Background()) 1907 1908 defer execStatements(t, []string{ 1909 "drop table stream1", 1910 "drop table stream2", 1911 }) 1912 1913 testcases := []testcase{{ 1914 input: []string{ 1915 "set @@session.binlog_format='STATEMENT'", 1916 "begin", 1917 "insert into stream1 values (1, 'aaa')", 1918 "update stream1 set val='bbb' where id = 1", 1919 "delete from stream1 where id = 1", 1920 "commit", 1921 "set @@session.binlog_format='ROW'", 1922 }, 1923 output: [][]string{{ 1924 `begin`, 1925 `type:INSERT dml:"insert into stream1 values (1, 'aaa')"`, 1926 `type:UPDATE dml:"update stream1 set val='bbb' where id = 1"`, 1927 `type:DELETE dml:"delete from stream1 where id = 1"`, 1928 `gtid`, 1929 `commit`, 1930 }}, 1931 }} 1932 runCases(t, nil, testcases, "", nil) 1933 } 1934 1935 func TestHeartbeat(t *testing.T) { 1936 if testing.Short() { 1937 t.Skip() 1938 } 1939 1940 ctx, cancel := context.WithCancel(context.Background()) 1941 defer cancel() 1942 1943 wg, ch := startStream(ctx, t, nil, "", nil) 1944 defer wg.Wait() 1945 evs := <-ch 1946 require.Equal(t, 1, len(evs)) 1947 assert.Equal(t, binlogdatapb.VEventType_HEARTBEAT, evs[0].Type) 1948 cancel() 1949 } 1950 1951 func TestNoFutureGTID(t *testing.T) { 1952 if testing.Short() { 1953 t.Skip() 1954 } 1955 1956 // Execute something to make sure we have ranges in GTIDs. 1957 execStatements(t, []string{ 1958 "create table stream1(id int, val varbinary(128), primary key(id))", 1959 }) 1960 defer execStatements(t, []string{ 1961 "drop table stream1", 1962 }) 1963 engine.se.Reload(context.Background()) 1964 1965 pos := primaryPosition(t) 1966 t.Logf("current position: %v", pos) 1967 // Both mysql and mariadb have '-' in their gtids. 1968 // Invent a GTID in the future. 1969 index := strings.LastIndexByte(pos, '-') 1970 num, err := strconv.Atoi(pos[index+1:]) 1971 require.NoError(t, err) 1972 future := pos[:index+1] + fmt.Sprintf("%d", num+1) 1973 t.Logf("future position: %v", future) 1974 1975 ctx, cancel := context.WithCancel(context.Background()) 1976 defer cancel() 1977 1978 ch := make(chan []*binlogdatapb.VEvent) 1979 go func() { 1980 for range ch { 1981 } 1982 }() 1983 defer close(ch) 1984 err = vstream(ctx, t, future, nil, nil, ch) 1985 want := "GTIDSet Mismatch" 1986 if err == nil || !strings.Contains(err.Error(), want) { 1987 t.Errorf("err: %v, must contain %s", err, want) 1988 } 1989 } 1990 1991 func TestFilteredMultipleWhere(t *testing.T) { 1992 if testing.Short() { 1993 t.Skip() 1994 } 1995 1996 execStatements(t, []string{ 1997 "create table t1(id1 int, id2 int, id3 int, val varbinary(128), primary key(id1))", 1998 }) 1999 defer execStatements(t, []string{ 2000 "drop table t1", 2001 }) 2002 engine.se.Reload(context.Background()) 2003 2004 setVSchema(t, shardedVSchema) 2005 defer env.SetVSchema("{}") 2006 2007 filter := &binlogdatapb.Filter{ 2008 Rules: []*binlogdatapb.Rule{{ 2009 Match: "t1", 2010 Filter: "select id1, val from t1 where in_keyrange('-80') and id2 = 200 and id3 = 1000 and val = 'newton'", 2011 }}, 2012 } 2013 2014 testcases := []testcase{{ 2015 input: []string{ 2016 "begin", 2017 "insert into t1 values (1, 100, 1000, 'kepler')", 2018 "insert into t1 values (2, 200, 1000, 'newton')", 2019 "insert into t1 values (3, 100, 2000, 'kepler')", 2020 "insert into t1 values (128, 200, 1000, 'newton')", 2021 "insert into t1 values (5, 200, 2000, 'kepler')", 2022 "insert into t1 values (129, 200, 1000, 'kepler')", 2023 "commit", 2024 }, 2025 output: [][]string{{ 2026 `begin`, 2027 `type:FIELD field_event:{table_name:"t1" fields:{name:"id1" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id1" column_length:11 charset:63 column_type:"int(11)"} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63 column_type:"varbinary(128)"}}`, 2028 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:1 lengths:6 values:"2newton"}}}`, 2029 `type:ROW row_event:{table_name:"t1" row_changes:{after:{lengths:3 lengths:6 values:"128newton"}}}`, 2030 `gtid`, 2031 `commit`, 2032 }}, 2033 }} 2034 runCases(t, filter, testcases, "", nil) 2035 } 2036 2037 // TestGeneratedColumns just confirms that generated columns are sent in a vstream as expected 2038 func TestGeneratedColumns(t *testing.T) { 2039 flavor := strings.ToLower(env.Flavor) 2040 // Disable tests on percona (which identifies as mysql56) and mariadb platforms in CI since they 2041 // generated columns support was added in 5.7 and mariadb added mysql compatible generated columns in 10.2 2042 if !strings.Contains(flavor, "mysql57") && !strings.Contains(flavor, "mysql80") { 2043 return 2044 } 2045 execStatements(t, []string{ 2046 "create table t1(id int, val varbinary(6), val2 varbinary(6) as (concat(id, val)), val3 varbinary(6) as (concat(val, id)), id2 int, primary key(id))", 2047 }) 2048 defer execStatements(t, []string{ 2049 "drop table t1", 2050 }) 2051 engine.se.Reload(context.Background()) 2052 queries := []string{ 2053 "begin", 2054 "insert into t1(id, val, id2) values (1, 'aaa', 10)", 2055 "insert into t1(id, val, id2) values (2, 'bbb', 20)", 2056 "commit", 2057 } 2058 2059 fe := &TestFieldEvent{ 2060 table: "t1", 2061 db: "vttest", 2062 cols: []*TestColumn{ 2063 {name: "id", dataType: "INT32", colType: "", len: 11, charset: 63}, 2064 {name: "val", dataType: "VARBINARY", colType: "", len: 6, charset: 63}, 2065 {name: "val2", dataType: "VARBINARY", colType: "", len: 6, charset: 63}, 2066 {name: "val3", dataType: "VARBINARY", colType: "", len: 6, charset: 63}, 2067 {name: "id2", dataType: "INT32", colType: "", len: 11, charset: 63}, 2068 }, 2069 } 2070 2071 testcases := []testcase{{ 2072 input: queries, 2073 output: [][]string{{ 2074 `begin`, 2075 fe.String(), 2076 `type:ROW row_event:<table_name:"t1" row_changes:<after:<lengths:1 lengths:3 lengths:4 lengths:4 lengths:2 values:"1aaa1aaaaaa110" > > > `, 2077 `type:ROW row_event:<table_name:"t1" row_changes:<after:<lengths:1 lengths:3 lengths:4 lengths:4 lengths:2 values:"2bbb2bbbbbb220" > > > `, 2078 `gtid`, 2079 `commit`, 2080 }}, 2081 }} 2082 runCases(t, nil, testcases, "current", nil) 2083 } 2084 2085 func runCases(t *testing.T, filter *binlogdatapb.Filter, testcases []testcase, position string, tablePK []*binlogdatapb.TableLastPK) { 2086 2087 ctx, cancel := context.WithCancel(context.Background()) 2088 defer cancel() 2089 wg, ch := startStream(ctx, t, filter, position, tablePK) 2090 defer wg.Wait() 2091 // If position is 'current', we wait for a heartbeat to be 2092 // sure the vstreamer has started. 2093 if position == "current" { 2094 log.Infof("Starting stream with current position") 2095 expectLog(ctx, t, "current pos", ch, [][]string{{`gtid`, `type:OTHER`}}) 2096 } 2097 2098 log.Infof("Starting to run test cases") 2099 for _, tcase := range testcases { 2100 switch input := tcase.input.(type) { 2101 case []string: 2102 execStatements(t, input) 2103 case string: 2104 execStatement(t, input) 2105 default: 2106 t.Fatalf("unexpected input: %#v", input) 2107 } 2108 engine.se.Reload(ctx) 2109 expectLog(ctx, t, tcase.input, ch, tcase.output) 2110 } 2111 2112 cancel() 2113 if evs, ok := <-ch; ok { 2114 t.Fatalf("unexpected evs: %v", evs) 2115 } 2116 log.Infof("Last line of runCases") 2117 } 2118 2119 func expectLog(ctx context.Context, t *testing.T, input any, ch <-chan []*binlogdatapb.VEvent, output [][]string) { 2120 timer := time.NewTimer(1 * time.Minute) 2121 defer timer.Stop() 2122 for _, wantset := range output { 2123 var evs []*binlogdatapb.VEvent 2124 for { 2125 select { 2126 case allevs, ok := <-ch: 2127 if !ok { 2128 t.Fatal("expectLog: not ok, stream ended early") 2129 } 2130 for _, ev := range allevs { 2131 // Ignore spurious heartbeats that can happen on slow machines. 2132 if ev.Type == binlogdatapb.VEventType_HEARTBEAT { 2133 continue 2134 } 2135 if ev.Throttled { 2136 continue 2137 } 2138 evs = append(evs, ev) 2139 } 2140 case <-ctx.Done(): 2141 t.Fatalf("expectLog: Done(), stream ended early") 2142 case <-timer.C: 2143 t.Fatalf("expectLog: timed out waiting for events: %v", wantset) 2144 } 2145 if len(evs) != 0 { 2146 break 2147 } 2148 } 2149 if len(wantset) != len(evs) { 2150 t.Fatalf("%v: evs\n%v, want\n%v, >> got length %d, wanted length %d", input, evs, wantset, len(evs), len(wantset)) 2151 } 2152 for i, want := range wantset { 2153 // CurrentTime is not testable. 2154 evs[i].CurrentTime = 0 2155 evs[i].Keyspace = "" 2156 evs[i].Shard = "" 2157 switch want { 2158 case "begin": 2159 if evs[i].Type != binlogdatapb.VEventType_BEGIN { 2160 t.Fatalf("%v (%d): event: %v, want gtid or begin", input, i, evs[i]) 2161 } 2162 case "gtid": 2163 if evs[i].Type != binlogdatapb.VEventType_GTID { 2164 t.Fatalf("%v (%d): event: %v, want gtid", input, i, evs[i]) 2165 } 2166 case "lastpk": 2167 if evs[i].Type != binlogdatapb.VEventType_LASTPK { 2168 t.Fatalf("%v (%d): event: %v, want lastpk", input, i, evs[i]) 2169 } 2170 case "commit": 2171 if evs[i].Type != binlogdatapb.VEventType_COMMIT { 2172 t.Fatalf("%v (%d): event: %v, want commit", input, i, evs[i]) 2173 } 2174 case "other": 2175 if evs[i].Type != binlogdatapb.VEventType_OTHER { 2176 t.Fatalf("%v (%d): event: %v, want other", input, i, evs[i]) 2177 } 2178 case "ddl": 2179 if evs[i].Type != binlogdatapb.VEventType_DDL { 2180 t.Fatalf("%v (%d): event: %v, want ddl", input, i, evs[i]) 2181 } 2182 case "copy_completed": 2183 if evs[i].Type != binlogdatapb.VEventType_COPY_COMPLETED { 2184 t.Fatalf("%v (%d): event: %v, want copy_completed", input, i, evs[i]) 2185 } 2186 default: 2187 evs[i].Timestamp = 0 2188 if evs[i].Type == binlogdatapb.VEventType_FIELD { 2189 for j := range evs[i].FieldEvent.Fields { 2190 evs[i].FieldEvent.Fields[j].Flags = 0 2191 if ignoreKeyspaceShardInFieldAndRowEvents { 2192 evs[i].FieldEvent.Keyspace = "" 2193 evs[i].FieldEvent.Shard = "" 2194 } 2195 } 2196 } 2197 if ignoreKeyspaceShardInFieldAndRowEvents && evs[i].Type == binlogdatapb.VEventType_ROW { 2198 evs[i].RowEvent.Keyspace = "" 2199 evs[i].RowEvent.Shard = "" 2200 } 2201 want = env.RemoveAnyDeprecatedDisplayWidths(want) 2202 if got := fmt.Sprintf("%v", evs[i]); got != want { 2203 log.Errorf("%v (%d): event:\n%q, want\n%q", input, i, got, want) 2204 t.Fatalf("%v (%d): event:\n%q, want\n%q", input, i, got, want) 2205 } 2206 } 2207 } 2208 } 2209 } 2210 2211 func startStream(ctx context.Context, t *testing.T, filter *binlogdatapb.Filter, position string, tablePKs []*binlogdatapb.TableLastPK) (*sync.WaitGroup, <-chan []*binlogdatapb.VEvent) { 2212 switch position { 2213 case "": 2214 position = primaryPosition(t) 2215 case "vscopy": 2216 position = "" 2217 } 2218 2219 wg := sync.WaitGroup{} 2220 wg.Add(1) 2221 ch := make(chan []*binlogdatapb.VEvent) 2222 2223 go func() { 2224 defer close(ch) 2225 defer wg.Done() 2226 vstream(ctx, t, position, tablePKs, filter, ch) 2227 }() 2228 return &wg, ch 2229 } 2230 2231 func vstream(ctx context.Context, t *testing.T, pos string, tablePKs []*binlogdatapb.TableLastPK, filter *binlogdatapb.Filter, ch chan []*binlogdatapb.VEvent) error { 2232 if filter == nil { 2233 filter = &binlogdatapb.Filter{ 2234 Rules: []*binlogdatapb.Rule{{ 2235 Match: "/.*/", 2236 }}, 2237 } 2238 } 2239 return engine.Stream(ctx, pos, tablePKs, filter, func(evs []*binlogdatapb.VEvent) error { 2240 timer := time.NewTimer(2 * time.Second) 2241 defer timer.Stop() 2242 2243 t.Logf("Received events: %v", evs) 2244 select { 2245 case ch <- evs: 2246 case <-ctx.Done(): 2247 return fmt.Errorf("engine.Stream Done() stream ended early") 2248 case <-timer.C: 2249 t.Log("VStream timed out waiting for events") 2250 return io.EOF 2251 } 2252 return nil 2253 }) 2254 } 2255 2256 func execStatement(t *testing.T, query string) { 2257 t.Helper() 2258 if err := env.Mysqld.ExecuteSuperQuery(context.Background(), query); err != nil { 2259 t.Fatal(err) 2260 } 2261 } 2262 2263 func execStatements(t *testing.T, queries []string) { 2264 if err := env.Mysqld.ExecuteSuperQueryList(context.Background(), queries); err != nil { 2265 t.Fatal(err) 2266 } 2267 } 2268 2269 func primaryPosition(t *testing.T) string { 2270 t.Helper() 2271 // We use the engine's cp because there is one test that overrides 2272 // the flavor to FilePos. If so, we have to obtain the position 2273 // in that flavor format. 2274 connParam, err := engine.env.Config().DB.DbaWithDB().MysqlParams() 2275 if err != nil { 2276 t.Fatal(err) 2277 } 2278 conn, err := mysql.Connect(context.Background(), connParam) 2279 if err != nil { 2280 t.Fatal(err) 2281 } 2282 defer conn.Close() 2283 pos, err := conn.PrimaryPosition() 2284 if err != nil { 2285 t.Fatal(err) 2286 } 2287 return mysql.EncodePosition(pos) 2288 } 2289 2290 func setVSchema(t *testing.T, vschema string) { 2291 t.Helper() 2292 2293 curCount := engine.vschemaUpdates.Get() 2294 if err := env.SetVSchema(vschema); err != nil { 2295 t.Fatal(err) 2296 } 2297 // Wait for curCount to go up. 2298 updated := false 2299 for i := 0; i < 10; i++ { 2300 if engine.vschemaUpdates.Get() != curCount { 2301 updated = true 2302 break 2303 } 2304 time.Sleep(10 * time.Millisecond) 2305 } 2306 if !updated { 2307 log.Infof("vschema did not get updated") 2308 t.Error("vschema did not get updated") 2309 } 2310 }