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

Unified Diff: sql/recovery.cc

Issue 1832173002: [sql] Database recovery system for Shortcuts. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: iOS SQLite doesn't support column names in view definition. Created 4 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 side-by-side diff with in-line comments
Download patch
Index: sql/recovery.cc
diff --git a/sql/recovery.cc b/sql/recovery.cc
index 7b9ca9f6fee65724a310e1f34620b97faf60bc7b..9e6932777f21e395822f9c0c27c6936fe59811c6 100644
--- a/sql/recovery.cc
+++ b/sql/recovery.cc
@@ -84,6 +84,31 @@ enum RecoveryEventType {
// No version key in recovery meta table.
RECOVERY_FAILED_META_NO_VERSION,
+ // Automatically recovered entire database successfully.
Mark P 2016/04/07 23:09:43 This comment sounds like it could equally apply to
Scott Hess - ex-Googler 2016/04/15 00:38:15 The earlier applies to a specific table, this appl
Mark P 2016/04/19 23:34:27 No, I thought you were being sloppy about database
+ RECOVERY_SUCCESS_AUTORECOVERDB,
+
+ // Database was so broken recovery couldn't be entered.
+ RECOVERY_FAILED_AUTORECOVERDB_BEGIN,
+
+ // Failed to copy schema to autorecover db.
+ RECOVERY_FAILED_AUTORECOVERDB_SCHEMA,
+
+ // Distinguish failure in querying schema from failure in creating schema.
Mark P 2016/04/08 20:12:06 Please clearly state these are subcategories of RE
Scott Hess - ex-Googler 2016/04/15 00:38:14 Done.
+ RECOVERY_FAILED_AUTORECOVERDB_SCHEMACREATE,
+ RECOVERY_FAILED_AUTORECOVERDB_SCHEMASELECT,
+
+ // Failed querying tables to recover. Should be impossible.
+ RECOVERY_FAILED_AUTORECOVERDB_NAMESELECT,
+
+ // Failed to recover an individual table.
+ RECOVERY_FAILED_AUTORECOVERDB_TABLE,
+
+ // Failed to recover [sqlite_sequence] table.
+ RECOVERY_FAILED_AUTORECOVERDB_SEQUENCE,
+
+ // Failed to recover triggers or views or virtual tables.
+ RECOVERY_FAILED_AUTORECOVERDB_AUX,
+
// Always keep this at the end.
RECOVERY_EVENT_MAX,
};
@@ -509,4 +534,161 @@ bool Recovery::GetMetaVersionNumber(int* version) {
return true;
}
+namespace {
+
+// Collect each SQL statement from [corrupt.sqlite_master] which starts with
+// |prefix|, and apply it to [main]. Skip 'sqlite_sequence', which will be
Mark P 2016/04/07 23:09:43 This comment is too broad. Below you explicitly a
Scott Hess - ex-Googler 2016/04/15 00:38:15 I agree that the example case isn't what I want, b
+// created on demand by SQLite if any tables use AUTOINCREMENT.
Mark P 2016/04/07 23:09:43 This comment should mention the meaning of the ret
Scott Hess - ex-Googler 2016/04/15 00:38:15 Done.
+bool SchemaCopyHelper(Connection* db, const char* prefix) {
+ const size_t prefix_len = strlen(prefix);
+ sql::Statement s(db->GetUniqueStatement(
+ "SELECT sql FROM corrupt.sqlite_master "
+ "WHERE substr(sql, 1, ?)=? AND name<>'sqlite_sequence'"));
Mark P 2016/04/07 23:09:43 What happens if prefix_length > length(sql)? What
Scott Hess - ex-Googler 2016/04/15 00:38:14 I think I'll just rewrite this as a full table sca
+ s.BindInt(0, prefix_len);
+ s.BindCString(1, prefix);
+
+ // TODO(shess): In case of row duplication when balancing a [sqlite_master]
+ // btree, this could inject IF NOT EXISTS. I suspect the occurance is too low
+ // to be worth the complexity, though.
Mark P 2016/04/07 23:09:43 Can you expand this slightly to be worth complexit
Scott Hess - ex-Googler 2016/04/15 00:38:15 I think I'll remove the comment entirely. A commo
+
Mark P 2016/04/07 23:09:43 This blank line here feels unnecessary (but then I
Scott Hess - ex-Googler 2016/04/15 00:38:15 Done.
+ while (s.Step()) {
Mark P 2016/04/07 23:09:43 Should all these executions be wrapped in a transa
Scott Hess - ex-Googler 2016/04/15 00:38:14 Recovery happens to a temporary database, which is
+ std::string sql = s.ColumnString(0);
+ sql.insert(prefix_len, "main.");
+ if (!db->Execute(sql.c_str())) {
+ RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SCHEMACREATE);
+ return false;
+ }
+ }
+ if (!s.Succeeded()) {
+ RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SCHEMASELECT);
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+// This method is derived from SQLite's vacuum.c. VACUUM operates very
+// similarily, creating a new database, populating the schema, then copying the
+// data.
+//
+// TODO(shess): This conservatively uses Rollback() rather than Unrecoverable().
+// With Rollback(), it is expected that the database will continue to generate
+// errors. Change the failure cases to Unrecoverable() if/when histogram
+// results indicate that everything is working reasonably.
+//
+// static
+void Recovery::RecoverDatabaseOrRaze(Connection* db,
+ const base::FilePath& db_path) {
+ scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
+ if (!recovery) {
+ // TODO(shess): If recovery can't even get started, consider Raze() or
+ // Delete().
+ //
+ // Reviewing histograms, the majority of Begin() failures are in the ATTACH
+ // (788k/month). This may happen for SQLITE_CANTOPEN and SQLITE_NOTADB
+ // cases which cannot be processed using writable_schema. The second
+ // biggest case is lack of vtable support on iOS, which is fixed but not yet
+ // in the channel (175k/month). Lastly, there were 3 failures in a month
Mark P 2016/04/08 20:12:06 Please don't put non-normalized numbers in comment
Scott Hess - ex-Googler 2016/04/15 00:38:14 Maybe I'll keep the comment in my local client, si
+ // opening the temporary table - in theory, that may work later, but in
+ // practice 3 in 1M might be a reasonable false-positive rate.
Mark P 2016/04/08 20:12:06 I think this last sentence isn't worth mentioning
Scott Hess - ex-Googler 2016/04/15 00:38:14 Acknowledged.
+ RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_BEGIN);
+ db->Poison();
Mark P 2016/04/08 20:12:06 In the header comment, you write the function will
Scott Hess - ex-Googler 2016/04/15 00:38:15 The originating caller's Open()/Close() calls shou
+ return;
+ }
+
+#if DCHECK_IS_ON()
Mark P 2016/04/08 20:12:06 I don't understand this block of code. That said,
Scott Hess - ex-Googler 2016/04/15 00:38:15 fts3 is a virtual table which we used to use in hi
+ // fts3 has an [x_segdir] table containing a column [end_block INTEGER]. But
+ // it actually stores either an integer or a text containing a pair of
+ // integers separated by a space. AutoRecoverTable() trust the INTEGER tag
+ // when setting up the recover vtable, so those rows get dropped. Setting
+ // that column to ANY may work, but maybe it's better to fail hard until
+ // someone needs to recover a virtual table.
+ if (db->is_open()) {
+ sql::Statement s(db->GetUniqueStatement(
+ "SELECT 1 FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE %'"));
+ DCHECK(!s.Step()) << "Recovery of virtual tables not supported";
+ }
+#endif
+
+ // TODO(shess): vacuum.c turns off checks and foreign keys.
+
+ // TODO(shess): vacuum.c turns synchronous=OFF for the target. I do not fully
+ // understand this, as the temporary db should not have a journal file at all.
+ // Perhaps it does in case of cache spill?
+
+ // Copy table schema from [corrupt] to [main].
+ if (!SchemaCopyHelper(recovery->db(), "CREATE TABLE ") ||
+ !SchemaCopyHelper(recovery->db(), "CREATE INDEX ") ||
+ !SchemaCopyHelper(recovery->db(), "CREATE UNIQUE INDEX ")) {
+ RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SCHEMA);
+ Recovery::Rollback(std::move(recovery));
Mark P 2016/04/08 20:12:06 Header talks about poisoning; why are you using a
Scott Hess - ex-Googler 2016/04/15 00:38:15 See comment I made about the RecoverDatabaseOrRaze
+ return;
+ }
+
+ // Run auto-recover against each table, skipping the sequence table. This is
+ // necessary because earlier table recovery may populate the sequence table,
+ // then copying the sequence table would create duplicates.
+ {
+ sql::Statement s(recovery->db()->GetUniqueStatement(
+ "SELECT name FROM sqlite_master WHERE sql LIKE 'CREATE TABLE %' "
+ "AND name!='sqlite_sequence'"));
Mark P 2016/04/08 20:12:06 AND != corrupt.sqlite_sequence ?
Scott Hess - ex-Googler 2016/04/15 00:38:15 sqlite_master is implicitly main.sqlite_master .
Mark P 2016/04/19 23:34:27 I'm sorry for making your brain explode. ;-)
+ while (s.Step()) {
+ const std::string name = s.ColumnString(0);
+ size_t rows_recovered;
+ if (!recovery->AutoRecoverTable(name.c_str(), &rows_recovered)) {
+ RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_TABLE);
+ Recovery::Rollback(std::move(recovery));
+ return;
+ }
+ }
+ if (!s.Succeeded()) {
+ RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NAMESELECT);
+ Recovery::Rollback(std::move(recovery));
+ return;
+ }
+ }
+
+ // Overwrite any sequences created.
+ if (recovery->db()->DoesTableExist("corrupt.sqlite_sequence")) {
+ ignore_result(recovery->db()->Execute("DELETE FROM main.sqlite_sequence"));
+ size_t rows_recovered;
+ if (!recovery->AutoRecoverTable("sqlite_sequence", &rows_recovered)) {
+ RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SEQUENCE);
+ Recovery::Rollback(std::move(recovery));
+ return;
+ }
+ }
+
+ // Copy triggers, views, and virtual tables directly to sqlite_master. Any
+ // tables they refer to should already exist.
+ // TODO(shess): The rootpage=0 test is from vacuum.c. Consider instead:
+ // sql LIKE "CREATE VIRTUAL TABLE %"
Mark P 2016/04/08 20:12:06 Do you have a sense of whether this will be differ
Scott Hess - ex-Googler 2016/04/15 00:38:15 I do not fully understand why vacuum.c uses the ro
+ char kCreateMetaItems[] =
+ "INSERT INTO main.sqlite_master "
+ "SELECT type, name, tbl_name, rootpage, sql "
+ "FROM corrupt.sqlite_master "
+ "WHERE type='view' OR type='trigger' OR (type='table' AND rootpage=0)";
+ if (!recovery->db()->Execute(kCreateMetaItems)) {
+ RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_AUX);
+ Recovery::Rollback(std::move(recovery));
+ return;
+ }
+
+ RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB);
+ ignore_result(Recovery::Recovered(std::move(recovery)));
+}
+
+// static
+bool Recovery::ShouldRecoverOrRaze(int extended_error) {
+ switch (extended_error & 0xFF) {
Mark P 2016/04/07 23:09:43 You do the same & 0xFF in connection.cc as well.
Scott Hess - ex-Googler 2016/04/15 00:38:15 Copied language from connection.cc. Unfortunately
+ case SQLITE_CANTOPEN:
+ case SQLITE_NOTADB:
+ case SQLITE_CORRUPT:
+ return true;
+ default:
+ return false;
+ }
+}
+
} // namespace sql

Powered by Google App Engine
This is Rietveld 408576698