| 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 "sql/recovery.h" | 5 #include "sql/recovery.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 #include <string> | 8 #include <string> |
| 9 #include <utility> | 9 #include <utility> |
| 10 | 10 |
| (...skipping 411 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 422 { | 422 { |
| 423 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 423 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 424 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); | 424 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 425 | 425 |
| 426 // Save a copy of the temp db's schema before recovering the table. | 426 // Save a copy of the temp db's schema before recovering the table. |
| 427 const char kTempSchemaSql[] = "SELECT name, sql FROM sqlite_temp_master"; | 427 const char kTempSchemaSql[] = "SELECT name, sql FROM sqlite_temp_master"; |
| 428 const std::string temp_schema( | 428 const std::string temp_schema( |
| 429 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n")); | 429 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n")); |
| 430 | 430 |
| 431 size_t rows = 0; | 431 size_t rows = 0; |
| 432 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); | 432 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows)); |
| 433 EXPECT_EQ(2u, rows); | 433 EXPECT_EQ(2u, rows); |
| 434 | 434 |
| 435 // Test that any additional temp tables were cleaned up. | 435 // Test that any additional temp tables were cleaned up. |
| 436 EXPECT_EQ(temp_schema, | 436 EXPECT_EQ(temp_schema, |
| 437 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n")); | 437 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n")); |
| 438 | 438 |
| 439 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); | 439 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); |
| 440 } | 440 } |
| 441 | 441 |
| 442 // Since the database was not corrupt, the entire schema and all | 442 // Since the database was not corrupt, the entire schema and all |
| 443 // data should be recovered. | 443 // data should be recovered. |
| 444 ASSERT_TRUE(Reopen()); | 444 ASSERT_TRUE(Reopen()); |
| 445 ASSERT_EQ(orig_schema, GetSchema(&db())); | 445 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 446 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); | 446 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 447 | 447 |
| 448 // Recovery fails if the target table doesn't exist. | 448 // Recovery fails if the target table doesn't exist. |
| 449 { | 449 { |
| 450 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 450 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 451 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); | 451 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 452 | 452 |
| 453 // TODO(shess): Should this failure implicitly lead to Raze()? | 453 // TODO(shess): Should this failure implicitly lead to Raze()? |
| 454 size_t rows = 0; | 454 size_t rows = 0; |
| 455 EXPECT_FALSE(recovery->AutoRecoverTable("y", 0, &rows)); | 455 EXPECT_FALSE(recovery->AutoRecoverTable("y", &rows)); |
| 456 | 456 |
| 457 sql::Recovery::Unrecoverable(std::move(recovery)); | 457 sql::Recovery::Unrecoverable(std::move(recovery)); |
| 458 } | 458 } |
| 459 } | 459 } |
| 460 | 460 |
| 461 // Test that default values correctly replace nulls. The recovery | 461 // Test that default values correctly replace nulls. The recovery |
| 462 // virtual table reads directly from the database, so DEFAULT is not | 462 // virtual table reads directly from the database, so DEFAULT is not |
| 463 // interpretted at that level. | 463 // interpretted at that level. |
| 464 TEST_F(SQLRecoveryTest, AutoRecoverTableWithDefault) { | 464 TEST_F(SQLRecoveryTest, AutoRecoverTableWithDefault) { |
| 465 ASSERT_TRUE(db().Execute("CREATE TABLE x (id INTEGER)")); | 465 ASSERT_TRUE(db().Execute("CREATE TABLE x (id INTEGER)")); |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 502 while ((pos = final_data.find("5|a'a")) != std::string::npos) { | 502 while ((pos = final_data.find("5|a'a")) != std::string::npos) { |
| 503 final_data.replace(pos, 5, "5|c'c"); | 503 final_data.replace(pos, 5, "5|c'c"); |
| 504 } | 504 } |
| 505 | 505 |
| 506 { | 506 { |
| 507 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 507 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 508 // Different default to detect which table provides the default. | 508 // Different default to detect which table provides the default. |
| 509 ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str())); | 509 ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str())); |
| 510 | 510 |
| 511 size_t rows = 0; | 511 size_t rows = 0; |
| 512 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); | 512 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows)); |
| 513 EXPECT_EQ(4u, rows); | 513 EXPECT_EQ(4u, rows); |
| 514 | 514 |
| 515 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); | 515 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); |
| 516 } | 516 } |
| 517 | 517 |
| 518 // Since the database was not corrupt, the entire schema and all | 518 // Since the database was not corrupt, the entire schema and all |
| 519 // data should be recovered. | 519 // data should be recovered. |
| 520 ASSERT_TRUE(Reopen()); | 520 ASSERT_TRUE(Reopen()); |
| 521 ASSERT_EQ(final_schema, GetSchema(&db())); | 521 ASSERT_EQ(final_schema, GetSchema(&db())); |
| 522 ASSERT_EQ(final_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); | 522 ASSERT_EQ(final_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| (...skipping 15 matching lines...) Expand all Loading... |
| 538 // detect that the recovery code actually ran. | 538 // detect that the recovery code actually ran. |
| 539 ASSERT_EQ(kOrigSchema, GetSchema(&db())); | 539 ASSERT_EQ(kOrigSchema, GetSchema(&db())); |
| 540 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); | 540 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 541 ASSERT_NE(kOrigSchema, GetSchema(&db())); | 541 ASSERT_NE(kOrigSchema, GetSchema(&db())); |
| 542 | 542 |
| 543 { | 543 { |
| 544 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 544 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 545 ASSERT_TRUE(recovery->db()->Execute(kFinalSchema)); | 545 ASSERT_TRUE(recovery->db()->Execute(kFinalSchema)); |
| 546 | 546 |
| 547 size_t rows = 0; | 547 size_t rows = 0; |
| 548 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); | 548 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows)); |
| 549 EXPECT_EQ(1u, rows); | 549 EXPECT_EQ(1u, rows); |
| 550 | 550 |
| 551 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); | 551 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); |
| 552 } | 552 } |
| 553 | 553 |
| 554 // The schema should be the same, but only one row of data should | 554 // The schema should be the same, but only one row of data should |
| 555 // have been recovered. | 555 // have been recovered. |
| 556 ASSERT_TRUE(Reopen()); | 556 ASSERT_TRUE(Reopen()); |
| 557 ASSERT_EQ(kFinalSchema, GetSchema(&db())); | 557 ASSERT_EQ(kFinalSchema, GetSchema(&db())); |
| 558 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; | 558 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| (...skipping 18 matching lines...) Expand all Loading... |
| 577 // Create a lame-duck table which will not be propagated by recovery to | 577 // Create a lame-duck table which will not be propagated by recovery to |
| 578 // detect that the recovery code actually ran. | 578 // detect that the recovery code actually ran. |
| 579 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); | 579 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 580 ASSERT_NE(orig_schema, GetSchema(&db())); | 580 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 581 | 581 |
| 582 { | 582 { |
| 583 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 583 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 584 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); | 584 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 585 | 585 |
| 586 size_t rows = 0; | 586 size_t rows = 0; |
| 587 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); | 587 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows)); |
| 588 EXPECT_EQ(2u, rows); | 588 EXPECT_EQ(2u, rows); |
| 589 | 589 |
| 590 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); | 590 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); |
| 591 } | 591 } |
| 592 | 592 |
| 593 // Since the database was not corrupt, the entire schema and all | 593 // Since the database was not corrupt, the entire schema and all |
| 594 // data should be recovered. | 594 // data should be recovered. |
| 595 ASSERT_TRUE(Reopen()); | 595 ASSERT_TRUE(Reopen()); |
| 596 ASSERT_EQ(orig_schema, GetSchema(&db())); | 596 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 597 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); | 597 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| (...skipping 24 matching lines...) Expand all Loading... |
| 622 // Create a lame-duck table which will not be propagated by recovery to | 622 // Create a lame-duck table which will not be propagated by recovery to |
| 623 // detect that the recovery code actually ran. | 623 // detect that the recovery code actually ran. |
| 624 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); | 624 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 625 ASSERT_NE(orig_schema, GetSchema(&db())); | 625 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 626 | 626 |
| 627 { | 627 { |
| 628 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 628 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 629 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); | 629 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 630 | 630 |
| 631 size_t rows = 0; | 631 size_t rows = 0; |
| 632 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); | 632 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows)); |
| 633 EXPECT_EQ(3u, rows); | 633 EXPECT_EQ(3u, rows); |
| 634 | 634 |
| 635 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); | 635 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); |
| 636 } | 636 } |
| 637 | 637 |
| 638 // Since the database was not corrupt, the entire schema and all | 638 // Since the database was not corrupt, the entire schema and all |
| 639 // data should be recovered. | 639 // data should be recovered. |
| 640 ASSERT_TRUE(Reopen()); | 640 ASSERT_TRUE(Reopen()); |
| 641 ASSERT_EQ(orig_schema, GetSchema(&db())); | 641 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 642 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); | 642 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 643 } | 643 } |
| 644 | 644 |
| 645 // Test |extend_columns| support. | 645 // Test recovering from a table with fewer columns than the target. |
| 646 TEST_F(SQLRecoveryTest, AutoRecoverTableExtendColumns) { | 646 TEST_F(SQLRecoveryTest, AutoRecoverTableMissingColumns) { |
| 647 const char kCreateSql[] = "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)"; | 647 const char kCreateSql[] = "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)"; |
| 648 const char kAlterSql[] = "ALTER TABLE x ADD COLUMN t1 TEXT DEFAULT 't'"; |
| 648 ASSERT_TRUE(db().Execute(kCreateSql)); | 649 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 649 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'This is')")); | 650 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'This is')")); |
| 650 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'That was')")); | 651 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'That was')")); |
| 651 | 652 |
| 652 // Save aside a copy of the original schema and data. | 653 // Generate the expected info by faking a table to match what recovery will |
| 654 // create. |
| 653 const std::string orig_schema(GetSchema(&db())); | 655 const std::string orig_schema(GetSchema(&db())); |
| 654 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; | 656 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 655 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); | 657 std::string expected_schema; |
| 658 std::string expected_data; |
| 659 { |
| 660 ASSERT_TRUE(db().BeginTransaction()); |
| 661 ASSERT_TRUE(db().Execute(kAlterSql)); |
| 656 | 662 |
| 657 // Modify the table to add a column, and add data to that column. | 663 expected_schema = GetSchema(&db()); |
| 658 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN t1 TEXT")); | 664 expected_data = ExecuteWithResults(&db(), kXSql, "|", "\n"); |
| 659 ASSERT_TRUE(db().Execute("UPDATE x SET t1 = 'a test'")); | |
| 660 ASSERT_NE(orig_schema, GetSchema(&db())); | |
| 661 ASSERT_NE(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); | |
| 662 | 665 |
| 666 db().RollbackTransaction(); |
| 667 } |
| 668 |
| 669 // Following tests are pointless if the rollback didn't work. |
| 670 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 671 |
| 672 // Recover the previous version of the table into the altered version. |
| 663 { | 673 { |
| 664 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 674 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 665 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); | 675 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 676 ASSERT_TRUE(recovery->db()->Execute(kAlterSql)); |
| 666 size_t rows = 0; | 677 size_t rows = 0; |
| 667 EXPECT_TRUE(recovery->AutoRecoverTable("x", 1, &rows)); | 678 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows)); |
| 668 EXPECT_EQ(2u, rows); | 679 EXPECT_EQ(2u, rows); |
| 669 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); | 680 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); |
| 670 } | 681 } |
| 671 | 682 |
| 672 // Since the database was not corrupt, the entire schema and all | 683 // Since the database was not corrupt, the entire schema and all |
| 673 // data should be recovered. | 684 // data should be recovered. |
| 674 ASSERT_TRUE(Reopen()); | 685 ASSERT_TRUE(Reopen()); |
| 675 ASSERT_EQ(orig_schema, GetSchema(&db())); | 686 ASSERT_EQ(expected_schema, GetSchema(&db())); |
| 676 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); | 687 ASSERT_EQ(expected_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 677 } | 688 } |
| 678 | 689 |
| 679 // Recover a golden file where an interior page has been manually modified so | 690 // Recover a golden file where an interior page has been manually modified so |
| 680 // that the number of cells is greater than will fit on a single page. This | 691 // that the number of cells is greater than will fit on a single page. This |
| 681 // case happened in <http://crbug.com/387868>. | 692 // case happened in <http://crbug.com/387868>. |
| 682 TEST_F(SQLRecoveryTest, Bug387868) { | 693 TEST_F(SQLRecoveryTest, Bug387868) { |
| 683 base::FilePath golden_path; | 694 base::FilePath golden_path; |
| 684 ASSERT_TRUE(PathService::Get(sql::test::DIR_TEST_DATA, &golden_path)); | 695 ASSERT_TRUE(PathService::Get(sql::test::DIR_TEST_DATA, &golden_path)); |
| 685 golden_path = golden_path.AppendASCII("recovery_387868"); | 696 golden_path = golden_path.AppendASCII("recovery_387868"); |
| 686 db().Close(); | 697 db().Close(); |
| 687 ASSERT_TRUE(base::CopyFile(golden_path, db_path())); | 698 ASSERT_TRUE(base::CopyFile(golden_path, db_path())); |
| 688 ASSERT_TRUE(Reopen()); | 699 ASSERT_TRUE(Reopen()); |
| 689 | 700 |
| 690 { | 701 { |
| 691 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 702 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 692 ASSERT_TRUE(recovery.get()); | 703 ASSERT_TRUE(recovery.get()); |
| 693 | 704 |
| 694 // Create the new version of the table. | 705 // Create the new version of the table. |
| 695 const char kCreateSql[] = | 706 const char kCreateSql[] = |
| 696 "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)"; | 707 "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)"; |
| 697 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); | 708 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 698 | 709 |
| 699 size_t rows = 0; | 710 size_t rows = 0; |
| 700 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); | 711 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows)); |
| 701 EXPECT_EQ(43u, rows); | 712 EXPECT_EQ(43u, rows); |
| 702 | 713 |
| 703 // Successfully recovered. | 714 // Successfully recovered. |
| 704 EXPECT_TRUE(sql::Recovery::Recovered(std::move(recovery))); | 715 EXPECT_TRUE(sql::Recovery::Recovered(std::move(recovery))); |
| 705 } | 716 } |
| 706 } | 717 } |
| 707 #endif // !defined(USE_SYSTEM_SQLITE) | 718 #endif // !defined(USE_SYSTEM_SQLITE) |
| 708 | 719 |
| 709 // Memory-mapped I/O interacts poorly with I/O errors. Make sure the recovery | 720 // Memory-mapped I/O interacts poorly with I/O errors. Make sure the recovery |
| 710 // database doesn't accidentally enable it. | 721 // database doesn't accidentally enable it. |
| 711 TEST_F(SQLRecoveryTest, NoMmap) { | 722 TEST_F(SQLRecoveryTest, NoMmap) { |
| 712 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 723 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 713 ASSERT_TRUE(recovery.get()); | 724 ASSERT_TRUE(recovery.get()); |
| 714 | 725 |
| 715 // In the current implementation, the PRAGMA successfully runs with no result | 726 // In the current implementation, the PRAGMA successfully runs with no result |
| 716 // rows. Running with a single result of |0| is also acceptable. | 727 // rows. Running with a single result of |0| is also acceptable. |
| 717 sql::Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size")); | 728 sql::Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size")); |
| 718 EXPECT_TRUE(!s.Step() || !s.ColumnInt64(0)); | 729 EXPECT_TRUE(!s.Step() || !s.ColumnInt64(0)); |
| 719 } | 730 } |
| 720 | 731 |
| 721 } // namespace | 732 } // namespace |
| OLD | NEW |