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 |