OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/bind.h" | 5 #include "base/bind.h" |
6 #include "base/file_util.h" | 6 #include "base/file_util.h" |
7 #include "base/files/scoped_temp_dir.h" | 7 #include "base/files/scoped_temp_dir.h" |
8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/strings/string_number_conversions.h" |
9 #include "base/strings/stringprintf.h" | 10 #include "base/strings/stringprintf.h" |
10 #include "sql/connection.h" | 11 #include "sql/connection.h" |
11 #include "sql/meta_table.h" | 12 #include "sql/meta_table.h" |
12 #include "sql/recovery.h" | 13 #include "sql/recovery.h" |
13 #include "sql/statement.h" | 14 #include "sql/statement.h" |
14 #include "sql/test/scoped_error_ignorer.h" | 15 #include "sql/test/scoped_error_ignorer.h" |
15 #include "testing/gtest/include/gtest/gtest.h" | 16 #include "testing/gtest/include/gtest/gtest.h" |
16 #include "third_party/sqlite/sqlite3.h" | 17 #include "third_party/sqlite/sqlite3.h" |
17 | 18 |
18 namespace { | 19 namespace { |
19 | 20 |
20 // Execute |sql|, and stringify the results with |column_sep| between | 21 // Execute |sql|, and stringify the results with |column_sep| between |
21 // columns and |row_sep| between rows. | 22 // columns and |row_sep| between rows. |
22 // TODO(shess): Promote this to a central testing helper. | 23 // TODO(shess): Promote this to a central testing helper. |
23 std::string ExecuteWithResults(sql::Connection* db, | 24 std::string ExecuteWithResults(sql::Connection* db, |
24 const char* sql, | 25 const char* sql, |
25 const char* column_sep, | 26 const char* column_sep, |
26 const char* row_sep) { | 27 const char* row_sep) { |
27 sql::Statement s(db->GetUniqueStatement(sql)); | 28 sql::Statement s(db->GetUniqueStatement(sql)); |
28 std::string ret; | 29 std::string ret; |
29 while (s.Step()) { | 30 while (s.Step()) { |
30 if (!ret.empty()) | 31 if (!ret.empty()) |
31 ret += row_sep; | 32 ret += row_sep; |
32 for (int i = 0; i < s.ColumnCount(); ++i) { | 33 for (int i = 0; i < s.ColumnCount(); ++i) { |
33 if (i > 0) | 34 if (i > 0) |
34 ret += column_sep; | 35 ret += column_sep; |
35 ret += s.ColumnString(i); | 36 if (s.ColumnType(i) == sql::COLUMN_TYPE_NULL) { |
| 37 ret += "<null>"; |
| 38 } else if (s.ColumnType(i) == sql::COLUMN_TYPE_BLOB) { |
| 39 ret += "<x'"; |
| 40 ret += base::HexEncode(s.ColumnBlob(i), s.ColumnByteLength(i)); |
| 41 ret += "'>"; |
| 42 } else { |
| 43 ret += s.ColumnString(i); |
| 44 } |
36 } | 45 } |
37 } | 46 } |
38 return ret; | 47 return ret; |
39 } | 48 } |
40 | 49 |
41 // Dump consistent human-readable representation of the database | 50 // Dump consistent human-readable representation of the database |
42 // schema. For tables or indices, this will contain the sql command | 51 // schema. For tables or indices, this will contain the sql command |
43 // to create the table or index. For certain automatic SQLite | 52 // to create the table or index. For certain automatic SQLite |
44 // structures with no sql, the name is used. | 53 // structures with no sql, the name is used. |
45 std::string GetSchema(sql::Connection* db) { | 54 std::string GetSchema(sql::Connection* db) { |
(...skipping 369 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
415 ASSERT_TRUE(Reopen()); | 424 ASSERT_TRUE(Reopen()); |
416 | 425 |
417 // The recovered table has consistency between the index and the table. | 426 // The recovered table has consistency between the index and the table. |
418 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); | 427 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); |
419 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); | 428 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); |
420 | 429 |
421 // The expected value was retained. | 430 // The expected value was retained. |
422 const char kSelectSql[] = "SELECT v FROM x WHERE id = 0"; | 431 const char kSelectSql[] = "SELECT v FROM x WHERE id = 0"; |
423 EXPECT_EQ("100", ExecuteWithResults(&db(), kSelectSql, "|", ",")); | 432 EXPECT_EQ("100", ExecuteWithResults(&db(), kSelectSql, "|", ",")); |
424 } | 433 } |
| 434 |
| 435 TEST_F(SQLRecoveryTest, Meta) { |
| 436 const int kVersion = 3; |
| 437 const int kCompatibleVersion = 2; |
| 438 |
| 439 { |
| 440 sql::MetaTable meta; |
| 441 EXPECT_TRUE(meta.Init(&db(), kVersion, kCompatibleVersion)); |
| 442 EXPECT_EQ(kVersion, meta.GetVersionNumber()); |
| 443 } |
| 444 |
| 445 // Test expected case where everything works. |
| 446 { |
| 447 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 448 EXPECT_TRUE(recovery->SetupMeta()); |
| 449 int version = 0; |
| 450 EXPECT_TRUE(recovery->GetMetaVersionNumber(&version)); |
| 451 EXPECT_EQ(kVersion, version); |
| 452 |
| 453 sql::Recovery::Rollback(recovery.Pass()); |
| 454 } |
| 455 ASSERT_TRUE(Reopen()); // Handle was poisoned. |
| 456 |
| 457 // Test version row missing. |
| 458 EXPECT_TRUE(db().Execute("DELETE FROM meta WHERE key = 'version'")); |
| 459 { |
| 460 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 461 EXPECT_TRUE(recovery->SetupMeta()); |
| 462 int version = 0; |
| 463 EXPECT_FALSE(recovery->GetMetaVersionNumber(&version)); |
| 464 EXPECT_EQ(0, version); |
| 465 |
| 466 sql::Recovery::Rollback(recovery.Pass()); |
| 467 } |
| 468 ASSERT_TRUE(Reopen()); // Handle was poisoned. |
| 469 |
| 470 // Test meta table missing. |
| 471 EXPECT_TRUE(db().Execute("DROP TABLE meta")); |
| 472 { |
| 473 sql::ScopedErrorIgnorer ignore_errors; |
| 474 ignore_errors.IgnoreError(SQLITE_CORRUPT); // From virtual table. |
| 475 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 476 EXPECT_FALSE(recovery->SetupMeta()); |
| 477 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); |
| 478 } |
| 479 } |
| 480 |
| 481 // Baseline AutoRecoverTable() test. |
| 482 TEST_F(SQLRecoveryTest, AutoRecoverTable) { |
| 483 // BIGINT and VARCHAR to test type affinity. |
| 484 const char kCreateSql[] = "CREATE TABLE x (id BIGINT, t TEXT, v VARCHAR)"; |
| 485 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 486 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (11, 'This is', 'a test')")); |
| 487 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, 'That was', 'a test')")); |
| 488 |
| 489 // Save aside a copy of the original schema and data. |
| 490 const std::string orig_schema(GetSchema(&db())); |
| 491 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 492 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 493 |
| 494 // Create a lame-duck table which will not be propagated by recovery to |
| 495 // detect that the recovery code actually ran. |
| 496 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 497 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 498 |
| 499 { |
| 500 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 501 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 502 |
| 503 // Save a copy of the temp db's schema before recovering the table. |
| 504 const char kTempSchemaSql[] = "SELECT name, sql FROM sqlite_temp_master"; |
| 505 const std::string temp_schema( |
| 506 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n")); |
| 507 |
| 508 size_t rows = 0; |
| 509 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); |
| 510 EXPECT_EQ(2u, rows); |
| 511 |
| 512 // Test that any additional temp tables were cleaned up. |
| 513 EXPECT_EQ(temp_schema, |
| 514 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n")); |
| 515 |
| 516 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 517 } |
| 518 |
| 519 // Since the database was not corrupt, the entire schema and all |
| 520 // data should be recovered. |
| 521 ASSERT_TRUE(Reopen()); |
| 522 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 523 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 524 |
| 525 // Recovery fails if the target table doesn't exist. |
| 526 { |
| 527 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 528 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 529 |
| 530 // TODO(shess): Should this failure implicitly lead to Raze()? |
| 531 size_t rows = 0; |
| 532 EXPECT_FALSE(recovery->AutoRecoverTable("y", 0, &rows)); |
| 533 |
| 534 sql::Recovery::Unrecoverable(recovery.Pass()); |
| 535 } |
| 536 } |
| 537 |
| 538 // Test that default values correctly replace nulls. The recovery |
| 539 // virtual table reads directly from the database, so DEFAULT is not |
| 540 // interpretted at that level. |
| 541 TEST_F(SQLRecoveryTest, AutoRecoverTableWithDefault) { |
| 542 ASSERT_TRUE(db().Execute("CREATE TABLE x (id INTEGER)")); |
| 543 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5)")); |
| 544 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15)")); |
| 545 |
| 546 // ALTER effectively leaves the new columns NULL in the first two |
| 547 // rows. The row with 17 will get the default injected at insert |
| 548 // time, while the row with 42 will get the actual value provided. |
| 549 // Embedded "'" to make sure default-handling continues to be quoted |
| 550 // correctly. |
| 551 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN t TEXT DEFAULT 'a''a'")); |
| 552 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN b BLOB DEFAULT x'AA55'")); |
| 553 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN i INT DEFAULT 93")); |
| 554 ASSERT_TRUE(db().Execute("INSERT INTO x (id) VALUES (17)")); |
| 555 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (42, 'b', x'1234', 12)")); |
| 556 |
| 557 // Save aside a copy of the original schema and data. |
| 558 const std::string orig_schema(GetSchema(&db())); |
| 559 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 560 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 561 |
| 562 // Create a lame-duck table which will not be propagated by recovery to |
| 563 // detect that the recovery code actually ran. |
| 564 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 565 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 566 |
| 567 // Mechanically adjust the stored schema and data to allow detecting |
| 568 // where the default value is coming from. The target table is just |
| 569 // like the original with the default for [t] changed, to signal |
| 570 // defaults coming from the recovery system. The two %5 rows should |
| 571 // get the target-table default for [t], while the others should get |
| 572 // the source-table default. |
| 573 std::string final_schema(orig_schema); |
| 574 std::string final_data(orig_data); |
| 575 size_t pos; |
| 576 while ((pos = final_schema.find("'a''a'")) != std::string::npos) { |
| 577 final_schema.replace(pos, 6, "'c''c'"); |
| 578 } |
| 579 while ((pos = final_data.find("5|a'a")) != std::string::npos) { |
| 580 final_data.replace(pos, 5, "5|c'c"); |
| 581 } |
| 582 |
| 583 { |
| 584 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 585 // Different default to detect which table provides the default. |
| 586 ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str())); |
| 587 |
| 588 size_t rows = 0; |
| 589 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); |
| 590 EXPECT_EQ(4u, rows); |
| 591 |
| 592 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 593 } |
| 594 |
| 595 // Since the database was not corrupt, the entire schema and all |
| 596 // data should be recovered. |
| 597 ASSERT_TRUE(Reopen()); |
| 598 ASSERT_EQ(final_schema, GetSchema(&db())); |
| 599 ASSERT_EQ(final_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 600 } |
| 601 |
| 602 // Test that rows with NULL in a NOT NULL column are filtered |
| 603 // correctly. In the wild, this would probably happen due to |
| 604 // corruption, but here it is simulated by recovering a table which |
| 605 // allowed nulls into a table which does not. |
| 606 TEST_F(SQLRecoveryTest, AutoRecoverTableNullFilter) { |
| 607 const char kOrigSchema[] = "CREATE TABLE x (id INTEGER, t TEXT)"; |
| 608 const char kFinalSchema[] = "CREATE TABLE x (id INTEGER, t TEXT NOT NULL)"; |
| 609 |
| 610 ASSERT_TRUE(db().Execute(kOrigSchema)); |
| 611 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, null)")); |
| 612 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15, 'this is a test')")); |
| 613 |
| 614 // Create a lame-duck table which will not be propagated by recovery to |
| 615 // detect that the recovery code actually ran. |
| 616 ASSERT_EQ(kOrigSchema, GetSchema(&db())); |
| 617 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 618 ASSERT_NE(kOrigSchema, GetSchema(&db())); |
| 619 |
| 620 { |
| 621 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 622 ASSERT_TRUE(recovery->db()->Execute(kFinalSchema)); |
| 623 |
| 624 size_t rows = 0; |
| 625 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); |
| 626 EXPECT_EQ(1u, rows); |
| 627 |
| 628 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 629 } |
| 630 |
| 631 // The schema should be the same, but only one row of data should |
| 632 // have been recovered. |
| 633 ASSERT_TRUE(Reopen()); |
| 634 ASSERT_EQ(kFinalSchema, GetSchema(&db())); |
| 635 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 636 ASSERT_EQ("15|this is a test", ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 637 } |
| 638 |
| 639 // Test AutoRecoverTable with a ROWID alias. |
| 640 TEST_F(SQLRecoveryTest, AutoRecoverTableWithRowid) { |
| 641 // The rowid alias is almost always the first column, intentionally |
| 642 // put it later. |
| 643 const char kCreateSql[] = |
| 644 "CREATE TABLE x (t TEXT, id INTEGER PRIMARY KEY NOT NULL)"; |
| 645 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 646 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test', null)")); |
| 647 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test', null)")); |
| 648 |
| 649 // Save aside a copy of the original schema and data. |
| 650 const std::string orig_schema(GetSchema(&db())); |
| 651 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 652 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 653 |
| 654 // Create a lame-duck table which will not be propagated by recovery to |
| 655 // detect that the recovery code actually ran. |
| 656 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 657 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 658 |
| 659 { |
| 660 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 661 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 662 |
| 663 size_t rows = 0; |
| 664 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); |
| 665 EXPECT_EQ(2u, rows); |
| 666 |
| 667 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 668 } |
| 669 |
| 670 // Since the database was not corrupt, the entire schema and all |
| 671 // data should be recovered. |
| 672 ASSERT_TRUE(Reopen()); |
| 673 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 674 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 675 } |
| 676 |
| 677 // Test that a compound primary key doesn't fire the ROWID code. |
| 678 TEST_F(SQLRecoveryTest, AutoRecoverTableWithCompoundKey) { |
| 679 const char kCreateSql[] = |
| 680 "CREATE TABLE x (" |
| 681 "id INTEGER NOT NULL," |
| 682 "id2 TEXT NOT NULL," |
| 683 "t TEXT," |
| 684 "PRIMARY KEY (id, id2)" |
| 685 ")"; |
| 686 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 687 |
| 688 // NOTE(shess): Do not accidentally use [id] 1, 2, 3, as those will |
| 689 // be the ROWID values. |
| 690 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'a', 'This is a test')")); |
| 691 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'b', 'That was a test')")); |
| 692 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'a', 'Another test')")); |
| 693 |
| 694 // Save aside a copy of the original schema and data. |
| 695 const std::string orig_schema(GetSchema(&db())); |
| 696 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 697 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 698 |
| 699 // Create a lame-duck table which will not be propagated by recovery to |
| 700 // detect that the recovery code actually ran. |
| 701 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 702 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 703 |
| 704 { |
| 705 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 706 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 707 |
| 708 size_t rows = 0; |
| 709 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); |
| 710 EXPECT_EQ(3u, rows); |
| 711 |
| 712 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 713 } |
| 714 |
| 715 // Since the database was not corrupt, the entire schema and all |
| 716 // data should be recovered. |
| 717 ASSERT_TRUE(Reopen()); |
| 718 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 719 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 720 } |
| 721 |
| 722 // Test |extend_columns| support. |
| 723 TEST_F(SQLRecoveryTest, AutoRecoverTableExtendColumns) { |
| 724 const char kCreateSql[] = "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)"; |
| 725 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 726 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'This is')")); |
| 727 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'That was')")); |
| 728 |
| 729 // Save aside a copy of the original schema and data. |
| 730 const std::string orig_schema(GetSchema(&db())); |
| 731 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 732 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 733 |
| 734 // Modify the table to add a column, and add data to that column. |
| 735 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN t1 TEXT")); |
| 736 ASSERT_TRUE(db().Execute("UPDATE x SET t1 = 'a test'")); |
| 737 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 738 ASSERT_NE(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 739 |
| 740 { |
| 741 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 742 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 743 size_t rows = 0; |
| 744 EXPECT_TRUE(recovery->AutoRecoverTable("x", 1, &rows)); |
| 745 EXPECT_EQ(2u, rows); |
| 746 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 747 } |
| 748 |
| 749 // Since the database was not corrupt, the entire schema and all |
| 750 // data should be recovered. |
| 751 ASSERT_TRUE(Reopen()); |
| 752 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 753 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 754 } |
425 #endif // !defined(USE_SYSTEM_SQLITE) | 755 #endif // !defined(USE_SYSTEM_SQLITE) |
426 | 756 |
427 } // namespace | 757 } // namespace |
OLD | NEW |