vitess.io/vitess@v0.16.2/go/mysql/endtoend/replication_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 endtoend 18 19 import ( 20 "bytes" 21 "fmt" 22 "reflect" 23 "strings" 24 "sync" 25 "testing" 26 "time" 27 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 31 "vitess.io/vitess/go/vt/vtgate/evalengine" 32 33 "context" 34 35 "vitess.io/vitess/go/mysql" 36 "vitess.io/vitess/go/sqltypes" 37 38 querypb "vitess.io/vitess/go/vt/proto/query" 39 ) 40 41 // connectForReplication is a helper method to connect for replication 42 // from the current binlog position. 43 func connectForReplication(t *testing.T, rbr bool) (*mysql.Conn, mysql.BinlogFormat) { 44 ctx := context.Background() 45 conn, err := mysql.Connect(ctx, &connParams) 46 if err != nil { 47 t.Fatal(err) 48 } 49 50 // We need to know if this is MariaDB, to set the right flag. 51 if conn.IsMariaDB() { 52 // This flag is required to get GTIDs from MariaDB. 53 t.Log("MariaDB: sensing SET @mariadb_slave_capability=4") 54 if _, err := conn.ExecuteFetch("SET @mariadb_slave_capability=4", 0, false); err != nil { 55 t.Fatalf("failed to set @mariadb_slave_capability=4: %v", err) 56 } 57 } 58 59 // Switch server to RBR if needed. 60 if rbr { 61 if _, err := conn.ExecuteFetch("SET GLOBAL binlog_format='ROW'", 0, false); err != nil { 62 t.Fatalf("SET GLOBAL binlog_format='ROW' failed: %v", err) 63 } 64 } 65 66 // First we get the current binlog position. 67 result, err := conn.ExecuteFetch("SHOW MASTER STATUS", 1, true) 68 require.NoError(t, err, "SHOW MASTER STATUS failed: %v", err) 69 70 if len(result.Fields) < 2 || result.Fields[0].Name != "File" || result.Fields[1].Name != "Position" || 71 len(result.Rows) != 1 { 72 t.Fatalf("SHOW MASTER STATUS returned unexpected result: %v", result) 73 } 74 file := result.Rows[0][0].ToString() 75 position, err := evalengine.ToUint64(result.Rows[0][1]) 76 require.NoError(t, err, "SHOW MASTER STATUS returned invalid position: %v", result.Rows[0][1]) 77 78 // Tell the server that we understand the format of events 79 // that will be used if binlog_checksum is enabled on the server. 80 if _, err := conn.ExecuteFetch("SET @master_binlog_checksum=@@global.binlog_checksum", 0, false); err != nil { 81 t.Fatalf("failed to set @master_binlog_checksum=@@global.binlog_checksum: %v", err) 82 } 83 84 // Write ComBinlogDump packet with to start streaming events from here. 85 if err := conn.WriteComBinlogDump(1, file, uint32(position), 0); err != nil { 86 t.Fatalf("WriteComBinlogDump failed: %v", err) 87 } 88 89 // Wait for the FORMAT_DESCRIPTION_EVENT 90 var f mysql.BinlogFormat 91 for { 92 be, err := conn.ReadBinlogEvent() 93 require.NoError(t, err, "ReadPacket failed: %v", err) 94 require.True(t, be.IsValid(), "NewMysql56BinlogEvent has an invalid packet: %v", be) 95 96 // Skip rotate packets. These are normal as first packets. 97 if be.IsRotate() { 98 t.Logf("Got a rotate packet: %v", be) 99 continue 100 } 101 // And we want a FORMAT_DESCRIPTION_EVENT. 102 // Print a few things about the event for sanity checks. 103 require.True(t, be.IsFormatDescription(), "Unexpected packet: %v", be) 104 105 f, err = be.Format() 106 require.NoError(t, err, "Format() returned error: %v", err) 107 108 t.Logf("Got a FORMAT_DESCRIPTION_EVENT packet: %v\nWith format: %v", be, f) 109 break 110 } 111 112 return conn, f 113 } 114 115 // TestReplicationConnectionClosing connects as a replication client, 116 // gets the first packet, then waits a few milliseconds and closes the 117 // connection. We should get the right error. 118 func TestReplicationConnectionClosing(t *testing.T) { 119 conn, _ := connectForReplication(t, false /* rbr */) 120 defer conn.Close() 121 122 // One go routine is waiting on events. 123 wg := sync.WaitGroup{} 124 wg.Add(1) 125 go func() { 126 defer wg.Done() 127 128 for { 129 data, err := conn.ReadPacket() 130 if err != nil { 131 serr, ok := err.(*mysql.SQLError) 132 assert.True(t, ok, "Got a non mysql.SQLError error: %v", err) 133 assert.Equal(t, mysql.CRServerLost, serr.Num, "Got an unexpected mysql.SQLError error: %v", serr) 134 135 // we got the right error, all good. 136 return 137 } 138 139 // Make sure it's a replication packet. 140 switch data[0] { 141 case mysql.OKPacket: 142 // What we expect, keep going. 143 case mysql.ErrPacket: 144 err := mysql.ParseErrorPacket(data) 145 t.Errorf("ReadPacket returned an error packet: %v", err) 146 default: 147 // Very unexpected. 148 t.Errorf("ReadPacket returned a weird packet: %v", data) 149 } 150 } 151 }() 152 153 // Connect and create a table. 154 ctx := context.Background() 155 dConn, err := mysql.Connect(ctx, &connParams) 156 if err != nil { 157 t.Fatal(err) 158 } 159 defer dConn.Close() 160 createTable := "create table replicationError(id int, name varchar(128), primary key(id))" 161 if _, err := dConn.ExecuteFetch(createTable, 0, false); err != nil { 162 t.Fatal(err) 163 } 164 result, err := dConn.ExecuteFetch("insert into replicationError(id, name) values(10, 'nice name')", 0, false) 165 require.NoError(t, err, "insert failed: %v", err) 166 167 if result.RowsAffected != 1 || len(result.Rows) != 0 { 168 t.Errorf("unexpected result for insert: %v", result) 169 } 170 if _, err := dConn.ExecuteFetch("drop table replicationError", 0, false); err != nil { 171 t.Fatalf("drop table failed: %v", err) 172 } 173 174 // wait for a few milliseconds. 175 time.Sleep(10 * time.Millisecond) 176 177 // Close the replication connection, hopefully while we are 178 // reading packets from it. 179 conn.Close() 180 181 // And we wait for background routine to exit. 182 wg.Wait() 183 } 184 185 func TestRowReplicationWithRealDatabase(t *testing.T) { 186 conn, f := connectForReplication(t, true /* rbr */) 187 defer conn.Close() 188 189 // Create a table, insert some data in it. 190 ctx := context.Background() 191 dConn, err := mysql.Connect(ctx, &connParams) 192 if err != nil { 193 t.Fatal(err) 194 } 195 defer dConn.Close() 196 createTable := "create table replication(id int, name varchar(128), primary key(id))" 197 if _, err := dConn.ExecuteFetch(createTable, 0, false); err != nil { 198 t.Fatal(err) 199 } 200 result, err := dConn.ExecuteFetch("insert into replication(id, name) values(10, 'nice name')", 0, false) 201 require.NoError(t, err, "insert failed: %v", err) 202 203 if result.RowsAffected != 1 || len(result.Rows) != 0 { 204 t.Errorf("unexpected result for insert: %v", result) 205 } 206 result, err = dConn.ExecuteFetch("update replication set name='nicer name' where id=10", 0, false) 207 require.NoError(t, err, "update failed: %v", err) 208 209 if result.RowsAffected != 1 || len(result.Rows) != 0 { 210 t.Errorf("unexpected result for update: %v", result) 211 } 212 result, err = dConn.ExecuteFetch("delete from replication where id=10", 0, false) 213 require.NoError(t, err, "delete failed: %v", err) 214 215 if result.RowsAffected != 1 || len(result.Rows) != 0 { 216 t.Errorf("unexpected result for delete: %v", result) 217 } 218 219 // Get the new events from the binlogs. 220 // Make sure we get four GTIDs, the table creation event, the 221 // table map event, and the insert/update/delete (with both a begin and a commit). 222 gtidCount := 0 223 beginCount := 0 224 commitCount := 0 225 gotCreateTable := false 226 gotTableMapEvent := false 227 gotInsert := false 228 gotUpdate := false 229 gotDelete := false 230 231 var tableID uint64 232 var tableMap *mysql.TableMap 233 234 // for i := 0; i < 6 && (gtidCount < 2 || !gotCreateTable || !gotTableMapEvent || !gotBegin || !gotInsert || !gotCommit); i++ { 235 for gtidCount < 4 || !gotCreateTable || !gotTableMapEvent || !gotInsert || !gotUpdate || !gotDelete || beginCount != 3 || commitCount != 3 { 236 be, err := conn.ReadBinlogEvent() 237 require.NoError(t, err, "ReadPacket failed: %v", err) 238 require.True(t, be.IsValid(), "read an invalid packet: %v", be) 239 240 be, _, err = be.StripChecksum(f) 241 require.NoError(t, err, "StripChecksum failed: %v", err) 242 243 switch { 244 case be.IsGTID(): 245 // We expect one of these at least. 246 gtid, hasBegin, err := be.GTID(f) 247 if err != nil { 248 t.Fatalf("GTID event is broken: %v", err) 249 } 250 t.Logf("Got GTID event: %v %v", gtid, hasBegin) 251 gtidCount++ 252 if hasBegin { 253 beginCount++ 254 } 255 case be.IsQuery(): 256 q, err := be.Query(f) 257 if err != nil { 258 t.Fatalf("Query event is broken: %v", err) 259 } 260 t.Logf("Got Query event: %v", q) 261 switch strings.ToLower(q.SQL) { 262 case createTable: 263 gotCreateTable = true 264 case "begin": 265 beginCount++ 266 case "commit": 267 commitCount++ 268 } 269 case be.IsXID(): 270 commitCount++ 271 t.Logf("Got XID event") 272 case be.IsTableMap(): 273 tableID = be.TableID(f) // This would be 0x00ffffff for an event to clear all table map entries. 274 var err error 275 tableMap, err = be.TableMap(f) 276 if err != nil { 277 t.Fatalf("TableMap event is broken: %v", err) 278 } 279 t.Logf("Got Table Map event: %v %v", tableID, tableMap) 280 if tableMap.Database != "vttest" || 281 tableMap.Name != "replication" || 282 len(tableMap.Types) != 2 || 283 tableMap.CanBeNull.Bit(0) || 284 !tableMap.CanBeNull.Bit(1) { 285 t.Errorf("got wrong TableMap: %v", tableMap) 286 } 287 gotTableMapEvent = true 288 case be.IsWriteRows(): 289 if got := be.TableID(f); got != tableID { 290 t.Fatalf("WriteRows event got table ID %v but was expecting %v", got, tableID) 291 } 292 wr, err := be.Rows(f, tableMap) 293 if err != nil { 294 t.Fatalf("Rows event is broken: %v", err) 295 } 296 297 // Check it has 2 rows, and first value is '10', second value is 'nice name'. 298 values, _ := wr.StringValuesForTests(tableMap, 0) 299 t.Logf("Got WriteRows event data: %v %v", wr, values) 300 if expected := []string{"10", "nice name"}; !reflect.DeepEqual(values, expected) { 301 t.Fatalf("StringValues returned %v, expected %v", values, expected) 302 } 303 304 gotInsert = true 305 case be.IsUpdateRows(): 306 if got := be.TableID(f); got != tableID { 307 t.Fatalf("UpdateRows event got table ID %v but was expecting %v", got, tableID) 308 } 309 ur, err := be.Rows(f, tableMap) 310 if err != nil { 311 t.Fatalf("UpdateRows event is broken: %v", err) 312 } 313 314 // Check it has 2 identify rows, and first value is '10', second value is 'nice name'. 315 values, _ := ur.StringIdentifiesForTests(tableMap, 0) 316 t.Logf("Got UpdateRows event identify: %v %v", ur, values) 317 if expected := []string{"10", "nice name"}; !reflect.DeepEqual(values, expected) { 318 t.Fatalf("StringIdentifies returned %v, expected %v", values, expected) 319 } 320 321 // Check it has 2 values rows, and first value is '10', second value is 'nicer name'. 322 values, _ = ur.StringValuesForTests(tableMap, 0) 323 t.Logf("Got UpdateRows event data: %v %v", ur, values) 324 if expected := []string{"10", "nicer name"}; !reflect.DeepEqual(values, expected) { 325 t.Fatalf("StringValues returned %v, expected %v", values, expected) 326 } 327 328 gotUpdate = true 329 case be.IsDeleteRows(): 330 if got := be.TableID(f); got != tableID { 331 t.Fatalf("DeleteRows event got table ID %v but was expecting %v", got, tableID) 332 } 333 dr, err := be.Rows(f, tableMap) 334 if err != nil { 335 t.Fatalf("DeleteRows event is broken: %v", err) 336 } 337 338 // Check it has 2 rows, and first value is '10', second value is 'nicer name'. 339 values, _ := dr.StringIdentifiesForTests(tableMap, 0) 340 t.Logf("Got DeleteRows event identify: %v %v", dr, values) 341 if expected := []string{"10", "nicer name"}; !reflect.DeepEqual(values, expected) { 342 t.Fatalf("StringIdentifies returned %v, expected %v", values, expected) 343 } 344 345 gotDelete = true 346 default: 347 t.Logf("Got unrelated event: %v", be) 348 } 349 } 350 351 // Drop the table, we're done. 352 if _, err := dConn.ExecuteFetch("drop table replication", 0, false); err != nil { 353 t.Fatal(err) 354 } 355 356 } 357 358 // TestRowReplicationTypes creates a table with all 359 // supported data types. Then we insert a row in it. then we re-build 360 // the SQL for the values, re-insert these. Then we select from the 361 // database and make sure both rows are identical. 362 func TestRowReplicationTypes(t *testing.T) { 363 // testcases are ordered by the types numbers in constants.go. 364 // Number are always unsigned, as we don't pass in sqltypes.Type. 365 testcases := []struct { 366 name string 367 createType string 368 createValue string 369 }{{ 370 // TINYINT 371 name: "tinytiny", 372 createType: "TINYINT UNSIGNED", 373 createValue: "145", 374 }, { 375 // SMALLINT 376 name: "smallish", 377 createType: "SMALLINT UNSIGNED", 378 createValue: "40000", 379 }, { 380 // INT 381 name: "regular_int", 382 createType: "INT UNSIGNED", 383 createValue: "4000000000", 384 }, { 385 // FLOAT 386 name: "floating", 387 createType: "FLOAT", 388 createValue: "-3.14159E-22", 389 }, { 390 // DOUBLE 391 name: "doubling", 392 createType: "DOUBLE", 393 createValue: "-3.14159265359E+12", 394 }, { 395 // TIMESTAMP (zero value) 396 name: "timestamp_zero", 397 createType: "TIMESTAMP", 398 createValue: "'0000-00-00 00:00:00'", 399 }, { 400 // TIMESTAMP (day precision) 401 name: "timestamp_day", 402 createType: "TIMESTAMP", 403 createValue: "'2012-11-10 00:00:00'", 404 }, { 405 // BIGINT 406 name: "big_int", 407 createType: "BIGINT UNSIGNED", 408 createValue: "10000000000000000000", 409 }, { 410 // MEDIUMINT 411 name: "mediumish", 412 createType: "MEDIUMINT UNSIGNED", 413 createValue: "10000000", 414 }, { 415 // DATE 416 name: "date_regular", 417 createType: "DATE", 418 createValue: "'1920-10-24'", 419 }, { 420 // TIME 421 name: "time_regular", 422 createType: "TIME", 423 createValue: "'120:44:58'", 424 }, { 425 // TIME 426 name: "time_neg", 427 createType: "TIME", 428 createValue: "'-212:44:58'", 429 }, { 430 // DATETIME 431 name: "datetime0", 432 createType: "DATETIME", 433 createValue: "'1020-08-23 12:44:58'", 434 }, { 435 // YEAR zero 436 name: "year0", 437 createType: "YEAR", 438 createValue: "0", 439 }, { 440 // YEAR 441 name: "year_nonzero", 442 createType: "YEAR", 443 createValue: "2052", 444 }, { 445 // VARCHAR 8 bits 446 name: "shortvc", 447 createType: "VARCHAR(30)", 448 createValue: "'short varchar'", 449 }, { 450 // VARCHAR 16 bits 451 name: "longvc", 452 createType: "VARCHAR(1000)", 453 createValue: "'long varchar'", 454 }, { 455 // BIT 456 name: "bit1", 457 createType: "BIT", 458 createValue: "b'1'", 459 }, { 460 // BIT 461 name: "bit6", 462 createType: "BIT(6)", 463 createValue: "b'100101'", 464 }, { 465 // BIT 466 name: "bit8", 467 createType: "BIT(8)", 468 createValue: "b'10100101'", 469 }, { 470 // BIT 471 name: "bit14", 472 createType: "BIT(14)", 473 createValue: "b'10100101000111'", 474 }, { 475 // BIT 476 name: "bit55", 477 createType: "BIT(55)", 478 createValue: "b'1010010100110100101001101001010011010010100110100101001'", 479 }, { 480 // BIT 481 name: "bit64", 482 createType: "BIT(64)", 483 createValue: "b'1111111111010010100110100101001101001010011010010100110100101001'", 484 }, { 485 // DECIMAL 486 name: "decimal2_1", 487 createType: "DECIMAL(2,1)", 488 createValue: "1.2", 489 }, { 490 // DECIMAL neg 491 name: "decimal2_1_neg", 492 createType: "DECIMAL(2,1)", 493 createValue: "-5.6", 494 }, { 495 // DECIMAL 496 name: "decimal4_2", 497 createType: "DECIMAL(4,2)", 498 createValue: "61.52", 499 }, { 500 // DECIMAL neg 501 name: "decimal4_2_neg", 502 createType: "DECIMAL(4,2)", 503 createValue: "-78.94", 504 }, { 505 // DECIMAL 506 name: "decimal6_3", 507 createType: "DECIMAL(6,3)", 508 createValue: "611.542", 509 }, { 510 // DECIMAL neg 511 name: "decimal6_3_neg", 512 createType: "DECIMAL(6,3)", 513 createValue: "-478.394", 514 }, { 515 // DECIMAL 516 name: "decimal8_4", 517 createType: "DECIMAL(8,4)", 518 createValue: "6311.5742", 519 }, { 520 // DECIMAL neg 521 name: "decimal8_4_neg", 522 createType: "DECIMAL(8,4)", 523 createValue: "-4778.3894", 524 }, { 525 // DECIMAL 526 name: "decimal10_5", 527 createType: "DECIMAL(10,5)", 528 createValue: "63711.57342", 529 }, { 530 // DECIMAL neg 531 name: "decimal10_5_neg", 532 createType: "DECIMAL(10,5)", 533 createValue: "-47378.38594", 534 }, { 535 // DECIMAL 536 name: "decimal12_6", 537 createType: "DECIMAL(12,6)", 538 createValue: "637311.557342", 539 }, { 540 // DECIMAL neg 541 name: "decimal12_6_neg", 542 createType: "DECIMAL(12,6)", 543 createValue: "-473788.385794", 544 }, { 545 // DECIMAL 546 name: "decimal14_7", 547 createType: "DECIMAL(14,7)", 548 createValue: "6375311.5574342", 549 }, { 550 // DECIMAL neg 551 name: "decimal14_7_neg", 552 createType: "DECIMAL(14,7)", 553 createValue: "-4732788.3853794", 554 }, { 555 // DECIMAL 556 name: "decimal16_8", 557 createType: "DECIMAL(16,8)", 558 createValue: "63375311.54574342", 559 }, { 560 // DECIMAL neg 561 name: "decimal16_8_neg", 562 createType: "DECIMAL(16,8)", 563 createValue: "-47327788.38533794", 564 }, { 565 // DECIMAL 566 name: "decimal18_9", 567 createType: "DECIMAL(18,9)", 568 createValue: "633075311.545714342", 569 }, { 570 // DECIMAL neg 571 name: "decimal18_9_neg", 572 createType: "DECIMAL(18,9)", 573 createValue: "-473327788.385033794", 574 }, { 575 // DECIMAL 576 name: "decimal20_10", 577 createType: "DECIMAL(20,10)", 578 createValue: "6330375311.5405714342", 579 }, { 580 // DECIMAL neg 581 name: "decimal20_10_neg", 582 createType: "DECIMAL(20,10)", 583 createValue: "-4731327788.3850337294", 584 }, { 585 // DECIMAL lots of left digits 586 name: "decimal34_0", 587 createType: "DECIMAL(34,0)", 588 createValue: "8765432345678987654345432123456786", 589 }, { 590 // DECIMAL lots of left digits neg 591 name: "decimal34_0_neg", 592 createType: "DECIMAL(34,0)", 593 createValue: "-8765432345678987654345432123456786", 594 }, { 595 // DECIMAL lots of right digits 596 name: "decimal34_30", 597 createType: "DECIMAL(34,30)", 598 createValue: "8765.432345678987654345432123456786", 599 }, { 600 // DECIMAL lots of right digits neg 601 name: "decimal34_30_neg", 602 createType: "DECIMAL(34,30)", 603 createValue: "-8765.432345678987654345432123456786", 604 }, { 605 // ENUM 606 name: "tshirtsize", 607 createType: "ENUM('x-small', 'small', 'medium', 'large', 'x-larg')", 608 createValue: "'large'", 609 }, { 610 // SET 611 name: "setnumbers", 612 createType: "SET('one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten')", 613 createValue: "'two,three,ten'", 614 }, { 615 // TINYBLOB 616 name: "tiny_blob", 617 createType: "TINYBLOB", 618 createValue: "'ab\\'cd'", 619 }, { 620 // BLOB 621 name: "bloby", 622 createType: "BLOB", 623 createValue: "'ab\\'cd'", 624 }, { 625 // MEDIUMBLOB 626 name: "medium_blob", 627 createType: "MEDIUMBLOB", 628 createValue: "'ab\\'cd'", 629 }, { 630 // LONGBLOB 631 name: "long_blob", 632 createType: "LONGBLOB", 633 createValue: "'ab\\'cd'", 634 }, { 635 // CHAR 8 bits 636 name: "shortchar", 637 createType: "CHAR(30)", 638 createValue: "'short char'", 639 }, { 640 // CHAR 9 bits (100 * 3 = 300, 256<=300<512) 641 name: "mediumchar", 642 createType: "CHAR(100)", 643 createValue: "'medium char'", 644 }, { 645 // CHAR 10 bits (250 * 3 = 750, 512<=750<124) 646 name: "longchar", 647 createType: "CHAR(250)", 648 createValue: "'long char'", 649 }, { 650 // GEOMETRY 651 name: "geo_stuff", 652 createType: "GEOMETRY", 653 createValue: "ST_GeomFromText('POINT(1 1)')", 654 }} 655 656 conn, f := connectForReplication(t, true /* rbr */) 657 defer conn.Close() 658 659 // MariaDB timestamp(N) is not supported by our RBR. See doc.go. 660 if !conn.IsMariaDB() { 661 testcases = append(testcases, []struct { 662 name string 663 createType string 664 createValue string 665 }{{ 666 // TIMESTAMP (second precision) 667 name: "timestamp_second", 668 createType: "TIMESTAMP", 669 createValue: "'2012-11-10 15:34:56'", 670 }, { 671 // TIMESTAMP (100 millisecond precision) 672 name: "timestamp_100millisecond", 673 createType: "TIMESTAMP(1)", 674 createValue: "'2012-11-10 15:34:56.6'", 675 }, { 676 // TIMESTAMP (10 millisecond precision) 677 name: "timestamp_10millisecond", 678 createType: "TIMESTAMP(2)", 679 createValue: "'2012-11-10 15:34:56.01'", 680 }, { 681 // TIMESTAMP (millisecond precision) 682 name: "timestamp_millisecond", 683 createType: "TIMESTAMP(3)", 684 createValue: "'2012-11-10 15:34:56.012'", 685 }, { 686 // TIMESTAMP (100 microsecond precision) 687 name: "timestamp_100microsecond", 688 createType: "TIMESTAMP(4)", 689 createValue: "'2012-11-10 15:34:56.0123'", 690 }, { 691 // TIMESTAMP (10 microsecond precision) 692 name: "timestamp_10microsecond", 693 createType: "TIMESTAMP(5)", 694 createValue: "'2012-11-10 15:34:56.01234'", 695 }, { 696 // TIMESTAMP (microsecond precision) 697 name: "timestamp_microsecond", 698 createType: "TIMESTAMP(6)", 699 createValue: "'2012-11-10 15:34:56.012345'", 700 }, { 701 // TIMESTAMP (0 with microsecond precision) 702 name: "timestamp_microsecond_z", 703 createType: "TIMESTAMP(6)", 704 createValue: "'0000-00-00 00:00:00.000000'", 705 }, { 706 // TIME 707 name: "time_100milli", 708 createType: "TIME(1)", 709 createValue: "'12:44:58.3'", 710 }, { 711 // TIME 712 name: "time_10milli", 713 createType: "TIME(2)", 714 createValue: "'412:44:58.01'", 715 }, { 716 // TIME 717 name: "time_milli", 718 createType: "TIME(3)", 719 createValue: "'-12:44:58.012'", 720 }, { 721 // TIME 722 name: "time_100micro", 723 createType: "TIME(4)", 724 createValue: "'12:44:58.0123'", 725 }, { 726 // TIME 727 name: "time_10micro", 728 createType: "TIME(5)", 729 createValue: "'12:44:58.01234'", 730 }, { 731 // TIME 732 name: "time_micro", 733 createType: "TIME(6)", 734 createValue: "'-12:44:58.012345'", 735 }, { 736 // DATETIME 737 name: "datetime1", 738 createType: "DATETIME(1)", 739 createValue: "'1020-08-23 12:44:58.8'", 740 }, { 741 // DATETIME 742 name: "datetime2", 743 createType: "DATETIME(2)", 744 createValue: "'1020-08-23 12:44:58.01'", 745 }, { 746 // DATETIME 747 name: "datetime3", 748 createType: "DATETIME(3)", 749 createValue: "'1020-08-23 12:44:58.012'", 750 }, { 751 // DATETIME 752 name: "datetime4", 753 createType: "DATETIME(4)", 754 createValue: "'1020-08-23 12:44:58.0123'", 755 }, { 756 // DATETIME 757 name: "datetime5", 758 createType: "DATETIME(5)", 759 createValue: "'1020-08-23 12:44:58.01234'", 760 }, { 761 // DATETIME 762 name: "datetime6", 763 createType: "DATETIME(6)", 764 createValue: "'1020-08-23 12:44:58.012345'", 765 }}...) 766 } 767 768 // JSON is only supported by MySQL 5.7+ 769 // However the binary format is not just the text version. 770 // So it doesn't work as expected. 771 if strings.HasPrefix(conn.ServerVersion, "5.7") { 772 testcases = append(testcases, []struct { 773 name string 774 createType string 775 createValue string 776 }{{ 777 name: "json1", 778 createType: "JSON", 779 createValue: "'{\"a\": 2}'", 780 }, { 781 name: "json2", 782 createType: "JSON", 783 createValue: "'[1,2]'", 784 }, { 785 name: "json3", 786 createType: "JSON", 787 createValue: "'{\"a\":\"b\", \"c\":\"d\",\"ab\":\"abc\", \"bc\": [\"x\", \"y\"]}'", 788 }, { 789 name: "json4", 790 createType: "JSON", 791 createValue: "'[\"here\", [\"I\", \"am\"], \"!!!\"]'", 792 }, { 793 name: "json5", 794 createType: "JSON", 795 createValue: "'\"scalar string\"'", 796 }, { 797 name: "json6", 798 createType: "JSON", 799 createValue: "'true'", 800 }, { 801 name: "json7", 802 createType: "JSON", 803 createValue: "'false'", 804 }, { 805 name: "json8", 806 createType: "JSON", 807 createValue: "'null'", 808 }, { 809 name: "json9", 810 createType: "JSON", 811 createValue: "'-1'", 812 }, { 813 name: "json10", 814 createType: "JSON", 815 createValue: "CAST(CAST(1 AS UNSIGNED) AS JSON)", 816 }, { 817 name: "json11", 818 createType: "JSON", 819 createValue: "'32767'", 820 }, { 821 name: "json12", 822 createType: "JSON", 823 createValue: "'32768'", 824 }, { 825 name: "json13", 826 createType: "JSON", 827 createValue: "'-32768'", 828 }, { 829 name: "json14", 830 createType: "JSON", 831 createValue: "'-32769'", 832 }, { 833 name: "json15", 834 createType: "JSON", 835 createValue: "'2147483647'", 836 }, { 837 name: "json16", 838 createType: "JSON", 839 createValue: "'2147483648'", 840 }, { 841 name: "json17", 842 createType: "JSON", 843 createValue: "'-2147483648'", 844 }, { 845 name: "json18", 846 createType: "JSON", 847 createValue: "'-2147483649'", 848 }, { 849 name: "json19", 850 createType: "JSON", 851 // FIXME: was "'18446744073709551615'", unsigned int representation differs from MySQL's which saves this as select 1.8446744073709552e19 852 // probably need to replace the json library: "github.com/spyzhov/ajson" 853 createValue: "'18446744073709551616'", 854 }, { 855 name: "json20", 856 createType: "JSON", 857 createValue: "'18446744073709551616'", 858 }, { 859 name: "json21", 860 createType: "JSON", 861 createValue: "'3.14159'", 862 }, { 863 name: "json22", 864 createType: "JSON", 865 createValue: "'{}'", 866 }, { 867 name: "json23", 868 createType: "JSON", 869 createValue: "'[]'", 870 }, { 871 name: "json24", 872 createType: "JSON", 873 createValue: "CAST(CAST('2015-01-24 23:24:25' AS DATETIME) AS JSON)", 874 }, { 875 name: "json25", 876 createType: "JSON", 877 createValue: "CAST(CAST('23:24:25' AS TIME) AS JSON)", 878 }, { 879 name: "json26", 880 createType: "JSON", 881 createValue: "CAST(CAST('23:24:26.12' AS TIME(3)) AS JSON)", 882 }, { 883 name: "json27", 884 createType: "JSON", 885 createValue: "CAST(CAST('2015-01-27' AS DATE) AS JSON)", 886 }, { 887 name: "json28", 888 createType: "JSON", 889 createValue: "CAST(TIMESTAMP'2015-01-28 23:24:28' AS JSON)", 890 }, { 891 name: "json29", 892 createType: "JSON", 893 createValue: "CAST(ST_GeomFromText('POINT(1 1)') AS JSON)", 894 }, { 895 // Decimal has special treatment. 896 name: "json30", 897 createType: "JSON", 898 createValue: "CAST(CAST('123456789.1234' AS DECIMAL(13,4)) AS JSON)", 899 // FIXME(alainjobart) opaque types are complicated. 900 // }, { 901 // This is a bit field. Opaque type in JSON. 902 // name: "json31", 903 // createType: "JSON", 904 // createValue: "CAST(x'cafe' AS JSON)", 905 }}...) 906 } 907 908 ctx := context.Background() 909 dConn, err := mysql.Connect(ctx, &connParams) 910 if err != nil { 911 t.Fatal(err) 912 } 913 defer dConn.Close() 914 915 // Set the connection time zone for execution of the 916 // statements to PST. That way we're sure to test the 917 // conversion for the TIMESTAMP types. 918 if _, err := dConn.ExecuteFetch("SET time_zone = '+08:00'", 0, false); err != nil { 919 t.Fatal(err) 920 } 921 922 // Create the table with all fields. 923 createTable := "create table replicationtypes(id int" 924 for _, tcase := range testcases { 925 createTable += fmt.Sprintf(", %v %v", tcase.name, tcase.createType) 926 } 927 createTable += ", primary key(id))" 928 if _, err := dConn.ExecuteFetch(createTable, 0, false); err != nil { 929 t.Fatal(err) 930 } 931 932 // Insert the value with all fields. 933 insert := "insert into replicationtypes set id=1" 934 for _, tcase := range testcases { 935 insert += fmt.Sprintf(", %v=%v", tcase.name, tcase.createValue) 936 } 937 938 result, err := dConn.ExecuteFetch(insert, 0, false) 939 require.NoError(t, err, "insert failed: %v", err) 940 941 if result.RowsAffected != 1 || len(result.Rows) != 0 { 942 t.Errorf("unexpected result for insert: %v", result) 943 } 944 945 // Get the new events from the binlogs. 946 // Only care about the Write event. 947 var tableID uint64 948 var tableMap *mysql.TableMap 949 var values []sqltypes.Value 950 951 for values == nil { 952 be, err := conn.ReadBinlogEvent() 953 require.NoError(t, err, "ReadPacket failed: %v", err) 954 require.True(t, be.IsValid(), "read an invalid packet: %v", be) 955 956 be, _, err = be.StripChecksum(f) 957 require.NoError(t, err, "StripChecksum failed: %v", err) 958 959 switch { 960 case be.IsTableMap(): 961 tableID = be.TableID(f) // This would be 0x00ffffff for an event to clear all table map entries. 962 var err error 963 tableMap, err = be.TableMap(f) 964 if err != nil { 965 t.Fatalf("TableMap event is broken: %v", err) 966 } 967 t.Logf("Got Table Map event: %v %v", tableID, tableMap) 968 if tableMap.Database != "vttest" || 969 tableMap.Name != "replicationtypes" || 970 len(tableMap.Types) != len(testcases)+1 || 971 tableMap.CanBeNull.Bit(0) { 972 t.Errorf("got wrong TableMap: %v", tableMap) 973 } 974 case be.IsWriteRows(): 975 if got := be.TableID(f); got != tableID { 976 t.Fatalf("WriteRows event got table ID %v but was expecting %v", got, tableID) 977 } 978 wr, err := be.Rows(f, tableMap) 979 if err != nil { 980 t.Fatalf("Rows event is broken: %v", err) 981 } 982 983 // Check it has the right values 984 values, err = valuesForTests(t, &wr, tableMap, 0) 985 if err != nil { 986 t.Fatalf("valuesForTests is broken: %v", err) 987 } 988 t.Logf("Got WriteRows event data: %v %v", wr, values) 989 if len(values) != len(testcases)+1 { 990 t.Fatalf("Got wrong length %v for values, was expecting %v", len(values), len(testcases)+1) 991 } 992 993 default: 994 t.Logf("Got unrelated event: %v", be) 995 } 996 } 997 998 // Insert a second row with the same data. 999 var sql bytes.Buffer 1000 sql.WriteString("insert into replicationtypes set id=2") 1001 for i, tcase := range testcases { 1002 sql.WriteString(", ") 1003 sql.WriteString(tcase.name) 1004 sql.WriteString(" = ") 1005 valueBytes, err := values[i+1].ToBytes() 1006 // Expression values are not supported with ToBytes 1007 if values[i+1].Type() != querypb.Type_EXPRESSION { 1008 require.NoError(t, err) 1009 } 1010 if values[i+1].Type() == querypb.Type_TIMESTAMP && !bytes.HasPrefix(valueBytes, mysql.ZeroTimestamp) { 1011 // Values in the binary log are UTC. Let's convert them 1012 // to whatever timezone the connection is using, 1013 // so MySQL properly converts them back to UTC. 1014 sql.WriteString("convert_tz(") 1015 values[i+1].EncodeSQL(&sql) 1016 sql.WriteString(", '+00:00', @@session.time_zone)") 1017 } else { 1018 if strings.Index(tcase.name, "json") == 0 { 1019 sql.WriteString("'" + string(values[i+1].Raw()) + "'") 1020 } else { 1021 values[i+1].EncodeSQL(&sql) 1022 } 1023 } 1024 } 1025 result, err = dConn.ExecuteFetch(sql.String(), 0, false) 1026 require.NoError(t, err, "insert '%v' failed: %v", sql.String(), err) 1027 1028 if result.RowsAffected != 1 || len(result.Rows) != 0 { 1029 t.Errorf("unexpected result for insert: %v", result) 1030 } 1031 t.Logf("Insert after getting event is: %v", sql.String()) 1032 1033 // Re-select both rows, make sure all columns are the same. 1034 stmt := "select id" 1035 for _, tcase := range testcases { 1036 stmt += ", " + tcase.name 1037 } 1038 stmt += " from replicationtypes" 1039 result, err = dConn.ExecuteFetch(stmt, 2, false) 1040 require.NoError(t, err, "select failed: %v", err) 1041 require.Equal(t, 2, len(result.Rows), "unexpected result for select: %v", result) 1042 1043 for i, tcase := range testcases { 1044 assert.True(t, reflect.DeepEqual(result.Rows[0][i+1], result.Rows[1][i+1]), "Field %v is not the same, got %v and %v", tcase.name, result.Rows[0][i+1], result.Rows[1][i+1]) 1045 1046 } 1047 1048 // Drop the table, we're done. 1049 if _, err := dConn.ExecuteFetch("drop table replicationtypes", 0, false); err != nil { 1050 t.Fatal(err) 1051 } 1052 1053 } 1054 1055 // valuesForTests is a helper method to return the sqltypes.Value 1056 // of all columns in a row in a Row. Only use it in tests, as the 1057 // returned values cannot be interpreted correctly without the schema. 1058 // We assume everything is unsigned in this method. 1059 func valuesForTests(t *testing.T, rs *mysql.Rows, tm *mysql.TableMap, rowIndex int) ([]sqltypes.Value, error) { 1060 var result []sqltypes.Value 1061 1062 valueIndex := 0 1063 data := rs.Rows[rowIndex].Data 1064 pos := 0 1065 for c := 0; c < rs.DataColumns.Count(); c++ { 1066 if !rs.DataColumns.Bit(c) { 1067 continue 1068 } 1069 1070 if rs.Rows[rowIndex].NullColumns.Bit(valueIndex) { 1071 // This column is represented, but its value is NULL. 1072 result = append(result, sqltypes.NULL) 1073 valueIndex++ 1074 continue 1075 } 1076 1077 // We have real data 1078 value, l, err := mysql.CellValue(data, pos, tm.Types[c], tm.Metadata[c], &querypb.Field{Type: querypb.Type_UINT64}) 1079 if err != nil { 1080 return nil, err 1081 } 1082 result = append(result, value) 1083 t.Logf(" %v: type=%v data=%v metadata=%v -> %v", c, tm.Types[c], data[pos:pos+l], tm.Metadata[c], value) 1084 pos += l 1085 valueIndex++ 1086 } 1087 1088 return result, nil 1089 }