vitess.io/vitess@v0.16.2/go/vt/binlog/binlog_streamer_rbr_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 binlog 18 19 import ( 20 "reflect" 21 "testing" 22 23 "context" 24 25 "vitess.io/vitess/go/mysql" 26 "vitess.io/vitess/go/vt/dbconfigs" 27 "vitess.io/vitess/go/vt/sqlparser" 28 "vitess.io/vitess/go/vt/vttablet/tabletserver/schema" 29 30 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 31 querypb "vitess.io/vitess/go/vt/proto/query" 32 ) 33 34 // This file tests the RBR events are parsed correctly. 35 36 func TestStreamerParseRBREvents(t *testing.T) { 37 f := mysql.NewMySQL56BinlogFormat() 38 s := mysql.NewFakeBinlogStream() 39 s.ServerID = 62344 40 41 // Create a schema.Engine for this test, with just one table. 42 // We only use the Columns. 43 se := schema.NewEngineForTests() 44 se.SetTableForTests(&schema.Table{ 45 Name: sqlparser.NewIdentifierCS("vt_a"), 46 Fields: []*querypb.Field{{ 47 Name: "id", 48 Type: querypb.Type_INT64, 49 }, { 50 Name: "message", 51 Type: querypb.Type_VARCHAR, 52 }}, 53 }) 54 55 // Create a tableMap event on the table. 56 tableID := uint64(0x102030405060) 57 tm := &mysql.TableMap{ 58 Flags: 0x8090, 59 Database: "vt_test_keyspace", 60 Name: "vt_a", 61 Types: []byte{ 62 mysql.TypeLong, 63 mysql.TypeVarchar, 64 }, 65 CanBeNull: mysql.NewServerBitmap(2), 66 Metadata: []uint16{ 67 0, 68 384, // A VARCHAR(128) in utf8 would result in 384. 69 }, 70 } 71 tm.CanBeNull.Set(1, true) 72 73 // Do an insert packet with all fields set. 74 insertRows := mysql.Rows{ 75 Flags: 0x1234, 76 DataColumns: mysql.NewServerBitmap(2), 77 Rows: []mysql.Row{ 78 { 79 NullColumns: mysql.NewServerBitmap(2), 80 Data: []byte{ 81 0x10, 0x20, 0x30, 0x40, // long 82 0x04, 0x00, // len('abcd') 83 'a', 'b', 'c', 'd', // 'abcd' 84 }, 85 }, 86 }, 87 } 88 insertRows.DataColumns.Set(0, true) 89 insertRows.DataColumns.Set(1, true) 90 91 // Do an update packet with all fields set. 92 updateRows := mysql.Rows{ 93 Flags: 0x1234, 94 IdentifyColumns: mysql.NewServerBitmap(2), 95 DataColumns: mysql.NewServerBitmap(2), 96 Rows: []mysql.Row{ 97 { 98 NullIdentifyColumns: mysql.NewServerBitmap(2), 99 NullColumns: mysql.NewServerBitmap(2), 100 Identify: []byte{ 101 0x10, 0x20, 0x30, 0x40, // long 102 0x03, 0x00, // len('abc') 103 'a', 'b', 'c', // 'abc' 104 }, 105 Data: []byte{ 106 0x10, 0x20, 0x30, 0x40, // long 107 0x04, 0x00, // len('abcd') 108 'a', 'b', 'c', 'd', // 'abcd' 109 }, 110 }, 111 }, 112 } 113 updateRows.IdentifyColumns.Set(0, true) 114 updateRows.IdentifyColumns.Set(1, true) 115 updateRows.DataColumns.Set(0, true) 116 updateRows.DataColumns.Set(1, true) 117 118 // Do an update packet with an identify set to NULL, and a 119 // value set to NULL. 120 updateRowsNull := mysql.Rows{ 121 Flags: 0x1234, 122 IdentifyColumns: mysql.NewServerBitmap(2), 123 DataColumns: mysql.NewServerBitmap(2), 124 Rows: []mysql.Row{ 125 { 126 NullIdentifyColumns: mysql.NewServerBitmap(2), 127 NullColumns: mysql.NewServerBitmap(2), 128 Identify: []byte{ 129 0x10, 0x20, 0x30, 0x40, // long 130 }, 131 Data: []byte{ 132 0x10, 0x20, 0x30, 0x40, // long 133 }, 134 }, 135 }, 136 } 137 updateRowsNull.IdentifyColumns.Set(0, true) 138 updateRowsNull.IdentifyColumns.Set(1, true) 139 updateRowsNull.DataColumns.Set(0, true) 140 updateRowsNull.DataColumns.Set(1, true) 141 updateRowsNull.Rows[0].NullIdentifyColumns.Set(1, true) 142 updateRowsNull.Rows[0].NullColumns.Set(1, true) 143 144 // Do a delete packet with all fields set. 145 deleteRows := mysql.Rows{ 146 Flags: 0x1234, 147 IdentifyColumns: mysql.NewServerBitmap(2), 148 Rows: []mysql.Row{ 149 { 150 NullIdentifyColumns: mysql.NewServerBitmap(2), 151 Identify: []byte{ 152 0x10, 0x20, 0x30, 0x40, // long 153 0x03, 0x00, // len('abc') 154 'a', 'b', 'c', // 'abc' 155 }, 156 }, 157 }, 158 } 159 deleteRows.IdentifyColumns.Set(0, true) 160 deleteRows.IdentifyColumns.Set(1, true) 161 162 input := []mysql.BinlogEvent{ 163 mysql.NewRotateEvent(f, s, 0, ""), 164 mysql.NewFormatDescriptionEvent(f, s), 165 mysql.NewTableMapEvent(f, s, tableID, tm), 166 mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 0xd}, false /* hasBegin */), 167 mysql.NewQueryEvent(f, s, mysql.Query{ 168 Database: "vt_test_keyspace", 169 SQL: "BEGIN"}), 170 mysql.NewWriteRowsEvent(f, s, tableID, insertRows), 171 mysql.NewUpdateRowsEvent(f, s, tableID, updateRows), 172 mysql.NewUpdateRowsEvent(f, s, tableID, updateRowsNull), 173 mysql.NewDeleteRowsEvent(f, s, tableID, deleteRows), 174 mysql.NewXIDEvent(f, s), 175 } 176 177 events := make(chan mysql.BinlogEvent) 178 errs := make(chan error) 179 180 want := []fullBinlogTransaction{ 181 { 182 statements: []FullBinlogStatement{ 183 { 184 Statement: &binlogdatapb.BinlogTransaction_Statement{ 185 Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, 186 Sql: []byte("SET TIMESTAMP=1407805592"), 187 }, 188 }, 189 { 190 Statement: &binlogdatapb.BinlogTransaction_Statement{ 191 Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT, 192 Sql: []byte("INSERT INTO vt_a SET id=1076895760, message='abcd'"), 193 }, 194 Table: "vt_a", 195 }, 196 { 197 Statement: &binlogdatapb.BinlogTransaction_Statement{ 198 Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, 199 Sql: []byte("SET TIMESTAMP=1407805592"), 200 }, 201 }, 202 { 203 Statement: &binlogdatapb.BinlogTransaction_Statement{ 204 Category: binlogdatapb.BinlogTransaction_Statement_BL_UPDATE, 205 Sql: []byte("UPDATE vt_a SET id=1076895760, message='abcd' WHERE id=1076895760 AND message='abc'"), 206 }, 207 Table: "vt_a", 208 }, 209 { 210 Statement: &binlogdatapb.BinlogTransaction_Statement{ 211 Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, 212 Sql: []byte("SET TIMESTAMP=1407805592"), 213 }, 214 }, 215 { 216 Statement: &binlogdatapb.BinlogTransaction_Statement{ 217 Category: binlogdatapb.BinlogTransaction_Statement_BL_UPDATE, 218 Sql: []byte("UPDATE vt_a SET id=1076895760, message=NULL WHERE id=1076895760 AND message IS NULL"), 219 }, 220 Table: "vt_a", 221 }, 222 { 223 Statement: &binlogdatapb.BinlogTransaction_Statement{ 224 Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, 225 Sql: []byte("SET TIMESTAMP=1407805592"), 226 }, 227 }, 228 { 229 Statement: &binlogdatapb.BinlogTransaction_Statement{ 230 Category: binlogdatapb.BinlogTransaction_Statement_BL_DELETE, 231 Sql: []byte("DELETE FROM vt_a WHERE id=1076895760 AND message='abc'"), 232 }, 233 Table: "vt_a", 234 }, 235 }, 236 eventToken: &querypb.EventToken{ 237 Timestamp: 1407805592, 238 Position: mysql.EncodePosition(mysql.Position{ 239 GTIDSet: mysql.MariadbGTIDSet{ 240 0: mysql.MariadbGTID{ 241 Domain: 0, 242 Server: 62344, 243 Sequence: 0x0d, 244 }, 245 }, 246 }), 247 }, 248 }, 249 } 250 var got []fullBinlogTransaction 251 sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error { 252 got = append(got, fullBinlogTransaction{ 253 eventToken: eventToken, 254 statements: statements, 255 }) 256 return nil 257 } 258 // Set mock mysql.ConnParams and dbconfig 259 mcp := &mysql.ConnParams{ 260 DbName: "vt_test_keyspace", 261 } 262 dbcfgs := dbconfigs.New(mcp) 263 264 bls := NewStreamer(dbcfgs, se, nil, mysql.Position{}, 0, sendTransaction) 265 266 go sendTestEvents(events, input) 267 _, err := bls.parseEvents(context.Background(), events, errs) 268 if err != ErrServerEOF { 269 t.Errorf("unexpected error: %v", err) 270 } 271 272 if !reflect.DeepEqual(got, want) { 273 t.Errorf("binlogConnStreamer.parseEvents(): got:\n%+v\nwant:\n%+v", got, want) 274 for i, fbt := range got { 275 t.Errorf("Got (%v)=%v", i, fbt.statements) 276 } 277 for i, fbt := range want { 278 t.Errorf("Want(%v)=%v", i, fbt.statements) 279 } 280 } 281 } 282 283 func TestStreamerParseRBRNameEscapes(t *testing.T) { 284 f := mysql.NewMySQL56BinlogFormat() 285 s := mysql.NewFakeBinlogStream() 286 s.ServerID = 62344 287 288 // Create a schema.Engine for this test using keyword names. 289 se := schema.NewEngineForTests() 290 se.SetTableForTests(&schema.Table{ 291 Name: sqlparser.NewIdentifierCS("insert"), 292 Fields: []*querypb.Field{{ 293 Name: "update", 294 Type: querypb.Type_INT64, 295 }, { 296 Name: "delete", 297 Type: querypb.Type_VARCHAR, 298 }}, 299 }) 300 301 // Create a tableMap event on the table. 302 tableID := uint64(0x102030405060) 303 tm := &mysql.TableMap{ 304 Flags: 0x8090, 305 Database: "vt_test_keyspace", 306 Name: "insert", 307 Types: []byte{ 308 mysql.TypeLong, 309 mysql.TypeVarchar, 310 }, 311 CanBeNull: mysql.NewServerBitmap(2), 312 Metadata: []uint16{ 313 0, 314 384, // A VARCHAR(128) in utf8 would result in 384. 315 }, 316 } 317 tm.CanBeNull.Set(1, true) 318 319 // Do an insert packet with all fields set. 320 insertRows := mysql.Rows{ 321 Flags: 0x1234, 322 DataColumns: mysql.NewServerBitmap(2), 323 Rows: []mysql.Row{ 324 { 325 NullColumns: mysql.NewServerBitmap(2), 326 Data: []byte{ 327 0x10, 0x20, 0x30, 0x40, // long 328 0x04, 0x00, // len('abcd') 329 'a', 'b', 'c', 'd', // 'abcd' 330 }, 331 }, 332 }, 333 } 334 insertRows.DataColumns.Set(0, true) 335 insertRows.DataColumns.Set(1, true) 336 337 // Do an update packet with all fields set. 338 updateRows := mysql.Rows{ 339 Flags: 0x1234, 340 IdentifyColumns: mysql.NewServerBitmap(2), 341 DataColumns: mysql.NewServerBitmap(2), 342 Rows: []mysql.Row{ 343 { 344 NullIdentifyColumns: mysql.NewServerBitmap(2), 345 NullColumns: mysql.NewServerBitmap(2), 346 Identify: []byte{ 347 0x10, 0x20, 0x30, 0x40, // long 348 0x03, 0x00, // len('abc') 349 'a', 'b', 'c', // 'abc' 350 }, 351 Data: []byte{ 352 0x10, 0x20, 0x30, 0x40, // long 353 0x04, 0x00, // len('abcd') 354 'a', 'b', 'c', 'd', // 'abcd' 355 }, 356 }, 357 }, 358 } 359 updateRows.IdentifyColumns.Set(0, true) 360 updateRows.IdentifyColumns.Set(1, true) 361 updateRows.DataColumns.Set(0, true) 362 updateRows.DataColumns.Set(1, true) 363 364 // Do an update packet with an identify set to NULL, and a 365 // value set to NULL. 366 updateRowsNull := mysql.Rows{ 367 Flags: 0x1234, 368 IdentifyColumns: mysql.NewServerBitmap(2), 369 DataColumns: mysql.NewServerBitmap(2), 370 Rows: []mysql.Row{ 371 { 372 NullIdentifyColumns: mysql.NewServerBitmap(2), 373 NullColumns: mysql.NewServerBitmap(2), 374 Identify: []byte{ 375 0x10, 0x20, 0x30, 0x40, // long 376 }, 377 Data: []byte{ 378 0x10, 0x20, 0x30, 0x40, // long 379 }, 380 }, 381 }, 382 } 383 updateRowsNull.IdentifyColumns.Set(0, true) 384 updateRowsNull.IdentifyColumns.Set(1, true) 385 updateRowsNull.DataColumns.Set(0, true) 386 updateRowsNull.DataColumns.Set(1, true) 387 updateRowsNull.Rows[0].NullIdentifyColumns.Set(1, true) 388 updateRowsNull.Rows[0].NullColumns.Set(1, true) 389 390 // Do a delete packet with all fields set. 391 deleteRows := mysql.Rows{ 392 Flags: 0x1234, 393 IdentifyColumns: mysql.NewServerBitmap(2), 394 Rows: []mysql.Row{ 395 { 396 NullIdentifyColumns: mysql.NewServerBitmap(2), 397 Identify: []byte{ 398 0x10, 0x20, 0x30, 0x40, // long 399 0x03, 0x00, // len('abc') 400 'a', 'b', 'c', // 'abc' 401 }, 402 }, 403 }, 404 } 405 deleteRows.IdentifyColumns.Set(0, true) 406 deleteRows.IdentifyColumns.Set(1, true) 407 408 input := []mysql.BinlogEvent{ 409 mysql.NewRotateEvent(f, s, 0, ""), 410 mysql.NewFormatDescriptionEvent(f, s), 411 mysql.NewTableMapEvent(f, s, tableID, tm), 412 mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 0xd}, false /* hasBegin */), 413 mysql.NewQueryEvent(f, s, mysql.Query{ 414 Database: "vt_test_keyspace", 415 SQL: "BEGIN"}), 416 mysql.NewWriteRowsEvent(f, s, tableID, insertRows), 417 mysql.NewUpdateRowsEvent(f, s, tableID, updateRows), 418 mysql.NewUpdateRowsEvent(f, s, tableID, updateRowsNull), 419 mysql.NewDeleteRowsEvent(f, s, tableID, deleteRows), 420 mysql.NewXIDEvent(f, s), 421 } 422 423 events := make(chan mysql.BinlogEvent) 424 errs := make(chan error) 425 426 want := []fullBinlogTransaction{ 427 { 428 statements: []FullBinlogStatement{ 429 { 430 Statement: &binlogdatapb.BinlogTransaction_Statement{ 431 Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, 432 Sql: []byte("SET TIMESTAMP=1407805592"), 433 }, 434 }, 435 { 436 Statement: &binlogdatapb.BinlogTransaction_Statement{ 437 Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT, 438 Sql: []byte("INSERT INTO `insert` SET `update`=1076895760, `delete`='abcd'"), 439 }, 440 Table: "insert", 441 }, 442 { 443 Statement: &binlogdatapb.BinlogTransaction_Statement{ 444 Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, 445 Sql: []byte("SET TIMESTAMP=1407805592"), 446 }, 447 }, 448 { 449 Statement: &binlogdatapb.BinlogTransaction_Statement{ 450 Category: binlogdatapb.BinlogTransaction_Statement_BL_UPDATE, 451 Sql: []byte("UPDATE `insert` SET `update`=1076895760, `delete`='abcd' WHERE `update`=1076895760 AND `delete`='abc'"), 452 }, 453 Table: "insert", 454 }, 455 { 456 Statement: &binlogdatapb.BinlogTransaction_Statement{ 457 Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, 458 Sql: []byte("SET TIMESTAMP=1407805592"), 459 }, 460 }, 461 { 462 Statement: &binlogdatapb.BinlogTransaction_Statement{ 463 Category: binlogdatapb.BinlogTransaction_Statement_BL_UPDATE, 464 Sql: []byte("UPDATE `insert` SET `update`=1076895760, `delete`=NULL WHERE `update`=1076895760 AND `delete` IS NULL"), 465 }, 466 Table: "insert", 467 }, 468 { 469 Statement: &binlogdatapb.BinlogTransaction_Statement{ 470 Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, 471 Sql: []byte("SET TIMESTAMP=1407805592"), 472 }, 473 }, 474 { 475 Statement: &binlogdatapb.BinlogTransaction_Statement{ 476 Category: binlogdatapb.BinlogTransaction_Statement_BL_DELETE, 477 Sql: []byte("DELETE FROM `insert` WHERE `update`=1076895760 AND `delete`='abc'"), 478 }, 479 Table: "insert", 480 }, 481 }, 482 eventToken: &querypb.EventToken{ 483 Timestamp: 1407805592, 484 Position: mysql.EncodePosition(mysql.Position{ 485 GTIDSet: mysql.MariadbGTIDSet{ 486 0: mysql.MariadbGTID{ 487 Domain: 0, 488 Server: 62344, 489 Sequence: 0x0d, 490 }, 491 }, 492 }), 493 }, 494 }, 495 } 496 var got []fullBinlogTransaction 497 sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error { 498 got = append(got, fullBinlogTransaction{ 499 eventToken: eventToken, 500 statements: statements, 501 }) 502 return nil 503 } 504 // Set mock mysql.ConnParams and dbconfig 505 mcp := &mysql.ConnParams{ 506 DbName: "vt_test_keyspace", 507 } 508 dbcfgs := dbconfigs.New(mcp) 509 510 bls := NewStreamer(dbcfgs, se, nil, mysql.Position{}, 0, sendTransaction) 511 512 go sendTestEvents(events, input) 513 _, err := bls.parseEvents(context.Background(), events, errs) 514 if err != ErrServerEOF { 515 t.Errorf("unexpected error: %v", err) 516 } 517 518 if !reflect.DeepEqual(got, want) { 519 t.Errorf("binlogConnStreamer.parseEvents(): got:\n%+v\nwant:\n%+v", got, want) 520 for i, fbt := range got { 521 t.Errorf("Got (%v)=%v", i, fbt.statements) 522 } 523 for i, fbt := range want { 524 t.Errorf("Want(%v)=%v", i, fbt.statements) 525 } 526 } 527 }