Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(497)

Side by Side Diff: sql/recovery.cc

Issue 2727553006: [sql] Convert thumbnails and top-sites databases to auto-recovery. (Closed)
Patch Set: tests and review comments Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sql/recovery.h ('k') | sql/recovery_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 8
9 #include "base/files/file_path.h" 9 #include "base/files/file_path.h"
10 #include "base/format_macros.h" 10 #include "base/format_macros.h"
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after
119 RECOVERY_FAILED_AUTORECOVERDB_NOTADB_REOPEN, 119 RECOVERY_FAILED_AUTORECOVERDB_NOTADB_REOPEN,
120 120
121 // After SQLITE_NOTADB failure setting up for recovery, Delete() and Open() 121 // After SQLITE_NOTADB failure setting up for recovery, Delete() and Open()
122 // succeeded, then querying the database failed. 122 // succeeded, then querying the database failed.
123 RECOVERY_FAILED_AUTORECOVERDB_NOTADB_QUERY, 123 RECOVERY_FAILED_AUTORECOVERDB_NOTADB_QUERY,
124 124
125 // After SQLITE_NOTADB failure setting up for recovery, the database was 125 // After SQLITE_NOTADB failure setting up for recovery, the database was
126 // successfully deleted. 126 // successfully deleted.
127 RECOVERY_SUCCESS_AUTORECOVERDB_NOTADB_DELETE, 127 RECOVERY_SUCCESS_AUTORECOVERDB_NOTADB_DELETE,
128 128
129 // Failed to find required [meta.version] information.
130 RECOVERY_FAILED_AUTORECOVERDB_META_VERSION,
131
129 // Add new items before this one, always keep this one at the end. 132 // Add new items before this one, always keep this one at the end.
130 RECOVERY_EVENT_MAX, 133 RECOVERY_EVENT_MAX,
131 }; 134 };
132 135
133 void RecordRecoveryEvent(RecoveryEventType recovery_event) { 136 void RecordRecoveryEvent(RecoveryEventType recovery_event) {
134 UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents", 137 UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents",
135 recovery_event, RECOVERY_EVENT_MAX); 138 recovery_event, RECOVERY_EVENT_MAX);
136 } 139 }
137 140
138 } // namespace 141 } // namespace
(...skipping 456 matching lines...) Expand 10 before | Expand all | Expand 10 after
595 // This method is derived from SQLite's vacuum.c. VACUUM operates very 598 // This method is derived from SQLite's vacuum.c. VACUUM operates very
596 // similarily, creating a new database, populating the schema, then copying the 599 // similarily, creating a new database, populating the schema, then copying the
597 // data. 600 // data.
598 // 601 //
599 // TODO(shess): This conservatively uses Rollback() rather than Unrecoverable(). 602 // TODO(shess): This conservatively uses Rollback() rather than Unrecoverable().
600 // With Rollback(), it is expected that the database will continue to generate 603 // With Rollback(), it is expected that the database will continue to generate
601 // errors. Change the failure cases to Unrecoverable() if/when histogram 604 // errors. Change the failure cases to Unrecoverable() if/when histogram
602 // results indicate that everything is working reasonably. 605 // results indicate that everything is working reasonably.
603 // 606 //
604 // static 607 // static
605 void Recovery::RecoverDatabase(Connection* db, 608 std::unique_ptr<Recovery> Recovery::BeginRecoverDatabase(
606 const base::FilePath& db_path) { 609 Connection* db,
610 const base::FilePath& db_path) {
607 std::unique_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); 611 std::unique_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
608 if (!recovery) { 612 if (!recovery) {
609 // Close the underlying sqlite* handle. Windows does not allow deleting 613 // Close the underlying sqlite* handle. Windows does not allow deleting
610 // open files, and all platforms block opening a second sqlite3* handle 614 // open files, and all platforms block opening a second sqlite3* handle
611 // against a database when exclusive locking is set. 615 // against a database when exclusive locking is set.
612 db->Poison(); 616 db->Poison();
613 617
614 // Histograms from Recovery::Begin() show all current failures are in 618 // Histograms from Recovery::Begin() show all current failures are in
615 // attaching the corrupt database, with 2/3 being SQLITE_NOTADB. Don't 619 // attaching the corrupt database, with 2/3 being SQLITE_NOTADB. Don't
616 // delete the database except for that specific failure case. 620 // delete the database except for that specific failure case.
617 { 621 {
618 Connection probe_db; 622 Connection probe_db;
619 if (!probe_db.OpenInMemory() || 623 if (!probe_db.OpenInMemory() ||
620 probe_db.AttachDatabase(db_path, "corrupt") || 624 probe_db.AttachDatabase(db_path, "corrupt") ||
621 probe_db.GetErrorCode() != SQLITE_NOTADB) { 625 probe_db.GetErrorCode() != SQLITE_NOTADB) {
622 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_BEGIN); 626 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_BEGIN);
623 return; 627 return nullptr;
624 } 628 }
625 } 629 }
626 630
627 // The database has invalid data in the SQLite header, so it is almost 631 // The database has invalid data in the SQLite header, so it is almost
628 // certainly not recoverable without manual intervention (and likely not 632 // certainly not recoverable without manual intervention (and likely not
629 // recoverable _with_ manual intervention). Clear away the broken database. 633 // recoverable _with_ manual intervention). Clear away the broken database.
630 if (!sql::Connection::Delete(db_path)) { 634 if (!sql::Connection::Delete(db_path)) {
631 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_DELETE); 635 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_DELETE);
632 return; 636 return nullptr;
633 } 637 }
634 638
635 // Windows deletion is complicated by file scanners and malware - sometimes 639 // Windows deletion is complicated by file scanners and malware - sometimes
636 // Delete() appears to succeed, even though the file remains. The following 640 // Delete() appears to succeed, even though the file remains. The following
637 // attempts to track if this happens often enough to cause concern. 641 // attempts to track if this happens often enough to cause concern.
638 { 642 {
639 Connection probe_db; 643 Connection probe_db;
640 if (!probe_db.Open(db_path)) { 644 if (!probe_db.Open(db_path)) {
641 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_REOPEN); 645 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_REOPEN);
642 return; 646 return nullptr;
643 } 647 }
644 if (!probe_db.Execute("PRAGMA auto_vacuum")) { 648 if (!probe_db.Execute("PRAGMA auto_vacuum")) {
645 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_QUERY); 649 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_QUERY);
646 return; 650 return nullptr;
647 } 651 }
648 } 652 }
649 653
650 // The rest of the recovery code could be run on the re-opened database, but 654 // The rest of the recovery code could be run on the re-opened database, but
651 // the database is empty, so there would be no point. 655 // the database is empty, so there would be no point.
652 RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB_NOTADB_DELETE); 656 RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB_NOTADB_DELETE);
653 return; 657 return nullptr;
654 } 658 }
655 659
656 #if DCHECK_IS_ON() 660 #if DCHECK_IS_ON()
657 // This code silently fails to recover fts3 virtual tables. At this time no 661 // This code silently fails to recover fts3 virtual tables. At this time no
658 // browser database contain fts3 tables. Just to be safe, complain loudly if 662 // browser database contain fts3 tables. Just to be safe, complain loudly if
659 // the database contains virtual tables. 663 // the database contains virtual tables.
660 // 664 //
661 // fts3 has an [x_segdir] table containing a column [end_block INTEGER]. But 665 // fts3 has an [x_segdir] table containing a column [end_block INTEGER]. But
662 // it actually stores either an integer or a text containing a pair of 666 // it actually stores either an integer or a text containing a pair of
663 // integers separated by a space. AutoRecoverTable() trusts the INTEGER tag 667 // integers separated by a space. AutoRecoverTable() trusts the INTEGER tag
(...skipping 11 matching lines...) Expand all
675 // TODO(shess): vacuum.c turns synchronous=OFF for the target. I do not fully 679 // TODO(shess): vacuum.c turns synchronous=OFF for the target. I do not fully
676 // understand this, as the temporary db should not have a journal file at all. 680 // understand this, as the temporary db should not have a journal file at all.
677 // Perhaps it does in case of cache spill? 681 // Perhaps it does in case of cache spill?
678 682
679 // Copy table schema from [corrupt] to [main]. 683 // Copy table schema from [corrupt] to [main].
680 if (!SchemaCopyHelper(recovery->db(), "CREATE TABLE ") || 684 if (!SchemaCopyHelper(recovery->db(), "CREATE TABLE ") ||
681 !SchemaCopyHelper(recovery->db(), "CREATE INDEX ") || 685 !SchemaCopyHelper(recovery->db(), "CREATE INDEX ") ||
682 !SchemaCopyHelper(recovery->db(), "CREATE UNIQUE INDEX ")) { 686 !SchemaCopyHelper(recovery->db(), "CREATE UNIQUE INDEX ")) {
683 // No RecordRecoveryEvent() here because SchemaCopyHelper() already did. 687 // No RecordRecoveryEvent() here because SchemaCopyHelper() already did.
684 Recovery::Rollback(std::move(recovery)); 688 Recovery::Rollback(std::move(recovery));
685 return; 689 return nullptr;
686 } 690 }
687 691
688 // Run auto-recover against each table, skipping the sequence table. This is 692 // Run auto-recover against each table, skipping the sequence table. This is
689 // necessary because table recovery can create the sequence table as a side 693 // necessary because table recovery can create the sequence table as a side
690 // effect, so recovering that table inline could lead to duplicate data. 694 // effect, so recovering that table inline could lead to duplicate data.
691 { 695 {
692 sql::Statement s(recovery->db()->GetUniqueStatement( 696 sql::Statement s(recovery->db()->GetUniqueStatement(
693 "SELECT name FROM sqlite_master WHERE sql LIKE 'CREATE TABLE %' " 697 "SELECT name FROM sqlite_master WHERE sql LIKE 'CREATE TABLE %' "
694 "AND name!='sqlite_sequence'")); 698 "AND name!='sqlite_sequence'"));
695 while (s.Step()) { 699 while (s.Step()) {
696 const std::string name = s.ColumnString(0); 700 const std::string name = s.ColumnString(0);
697 size_t rows_recovered; 701 size_t rows_recovered;
698 if (!recovery->AutoRecoverTable(name.c_str(), &rows_recovered)) { 702 if (!recovery->AutoRecoverTable(name.c_str(), &rows_recovered)) {
699 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_TABLE); 703 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_TABLE);
700 Recovery::Rollback(std::move(recovery)); 704 Recovery::Rollback(std::move(recovery));
701 return; 705 return nullptr;
702 } 706 }
703 } 707 }
704 if (!s.Succeeded()) { 708 if (!s.Succeeded()) {
705 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NAMESELECT); 709 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NAMESELECT);
706 Recovery::Rollback(std::move(recovery)); 710 Recovery::Rollback(std::move(recovery));
707 return; 711 return nullptr;
708 } 712 }
709 } 713 }
710 714
711 // Overwrite any sequences created. 715 // Overwrite any sequences created.
712 if (recovery->db()->DoesTableExist("corrupt.sqlite_sequence")) { 716 if (recovery->db()->DoesTableExist("corrupt.sqlite_sequence")) {
713 ignore_result(recovery->db()->Execute("DELETE FROM main.sqlite_sequence")); 717 ignore_result(recovery->db()->Execute("DELETE FROM main.sqlite_sequence"));
714 size_t rows_recovered; 718 size_t rows_recovered;
715 if (!recovery->AutoRecoverTable("sqlite_sequence", &rows_recovered)) { 719 if (!recovery->AutoRecoverTable("sqlite_sequence", &rows_recovered)) {
716 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SEQUENCE); 720 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SEQUENCE);
717 Recovery::Rollback(std::move(recovery)); 721 Recovery::Rollback(std::move(recovery));
718 return; 722 return nullptr;
719 } 723 }
720 } 724 }
721 725
722 // Copy triggers and views directly to sqlite_master. Any tables they refer 726 // Copy triggers and views directly to sqlite_master. Any tables they refer
723 // to should already exist. 727 // to should already exist.
724 char kCreateMetaItems[] = 728 char kCreateMetaItems[] =
725 "INSERT INTO main.sqlite_master " 729 "INSERT INTO main.sqlite_master "
726 "SELECT type, name, tbl_name, rootpage, sql " 730 "SELECT type, name, tbl_name, rootpage, sql "
727 "FROM corrupt.sqlite_master WHERE type='view' OR type='trigger'"; 731 "FROM corrupt.sqlite_master WHERE type='view' OR type='trigger'";
728 if (!recovery->db()->Execute(kCreateMetaItems)) { 732 if (!recovery->db()->Execute(kCreateMetaItems)) {
729 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_AUX); 733 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_AUX);
730 Recovery::Rollback(std::move(recovery)); 734 Recovery::Rollback(std::move(recovery));
735 return nullptr;
736 }
737
738 RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB);
739 return recovery;
740 }
741
742 void Recovery::RecoverDatabase(Connection* db, const base::FilePath& db_path) {
743 std::unique_ptr<sql::Recovery> recovery = BeginRecoverDatabase(db, db_path);
744
745 // ignore_result() because BeginRecoverDatabase() and Recovered() already
746 // provide suitable histogram coverage.
747 if (recovery)
748 ignore_result(Recovery::Recovered(std::move(recovery)));
749 }
750
751 void Recovery::RecoverDatabaseWithMetaVersion(Connection* db,
752 const base::FilePath& db_path) {
753 std::unique_ptr<sql::Recovery> recovery = BeginRecoverDatabase(db, db_path);
754 if (!recovery)
755 return;
756
757 int version = 0;
758 if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) {
759 sql::Recovery::Unrecoverable(std::move(recovery));
760 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_META_VERSION);
731 return; 761 return;
732 } 762 }
733 763
734 RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB); 764 // ignore_result() because BeginRecoverDatabase() and Recovered() already
765 // provide suitable histogram coverage.
735 ignore_result(Recovery::Recovered(std::move(recovery))); 766 ignore_result(Recovery::Recovered(std::move(recovery)));
736 } 767 }
737 768
738 // static 769 // static
739 bool Recovery::ShouldRecover(int extended_error) { 770 bool Recovery::ShouldRecover(int extended_error) {
740 // Trim extended error codes. 771 // Trim extended error codes.
741 int error = extended_error & 0xFF; 772 int error = extended_error & 0xFF;
742 switch (error) { 773 switch (error) {
743 case SQLITE_NOTADB: 774 case SQLITE_NOTADB:
744 // SQLITE_NOTADB happens if the SQLite header is broken. Some earlier 775 // SQLITE_NOTADB happens if the SQLite header is broken. Some earlier
(...skipping 20 matching lines...) Expand all
765 // - SQLITE_READONLY - permissions could be fixed. 796 // - SQLITE_READONLY - permissions could be fixed.
766 // - SQLITE_IOERR - rewrite using new blocks. 797 // - SQLITE_IOERR - rewrite using new blocks.
767 // - SQLITE_FULL - recover in memory and rewrite subset of data. 798 // - SQLITE_FULL - recover in memory and rewrite subset of data.
768 799
769 default: 800 default:
770 return false; 801 return false;
771 } 802 }
772 } 803 }
773 804
774 } // namespace sql 805 } // namespace sql
OLDNEW
« no previous file with comments | « sql/recovery.h ('k') | sql/recovery_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698