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

Side by Side Diff: sql/recovery.cc

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

Powered by Google App Engine
This is Rietveld 408576698