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 3 // found in the LICENSE file. 4 5 #ifndef SQL_RECOVERY_H_ 6 #define SQL_RECOVERY_H_ 7 8 #include "base/basictypes.h" 9 10 #include "sql/connection.h" 11 12 namespace base { 13 class FilePath; 14 } 15 16 namespace sql { 17 18 // Recovery module for sql/. The basic idea is to create a fresh 19 // database and populate it with the recovered contents of the 20 // original database. If recovery is successful, the recovered 21 // database is backed up over the original database. If recovery is 22 // not successful, the original database is razed. In either case, 23 // the original handle is poisoned so that operations on the stack do 24 // not accidentally disrupt the restored data. 25 // 26 // { 27 // scoped_ptr<sql::Recovery> r = 28 // sql::Recovery::Begin(orig_db, orig_db_path); 29 // if (r) { 30 // // Create the schema to recover to. On failure, clear the 31 // // database. 32 // if (!r.db()->Execute(kCreateSchemaSql)) { 33 // sql::Recovery::Unrecoverable(r.Pass()); 34 // return; 35 // } 36 // 37 // // Recover data in "mytable". 38 // size_t rows_recovered = 0; 39 // if (!r.AutoRecoverTable("mytable", 0, &rows_recovered)) { 40 // sql::Recovery::Unrecoverable(r.Pass()); 41 // return; 42 // } 43 // 44 // // Manually cleanup additional constraints. 45 // if (!r.db()->Execute(kCleanupSql)) { 46 // sql::Recovery::Unrecoverable(r.Pass()); 47 // return; 48 // } 49 // 50 // // Commit the recovered data to the original database file. 51 // sql::Recovery::Recovered(r.Pass()); 52 // } 53 // } 54 // 55 // If Recovered() is not called, then RazeAndClose() is called on 56 // orig_db. 57 58 class SQL_EXPORT Recovery { 59 public: 60 ~Recovery(); 61 62 // This module is intended to be used in concert with a virtual 63 // table module (see third_party/sqlite/src/src/recover.c). If the 64 // build defines USE_SYSTEM_SQLITE, this module will not be present. 65 // TODO(shess): I am still debating how to handle this - perhaps it 66 // will just imply Unrecoverable(). This is exposed to allow tests 67 // to adapt to the cases, please do not rely on it in production 68 // code. 69 static bool FullRecoverySupported(); 70 71 // Begin the recovery process by opening a temporary database handle 72 // and attach the existing database to it at "corrupt". To prevent 73 // deadlock, all transactions on |connection| are rolled back. 74 // 75 // Returns NULL in case of failure, with no cleanup done on the 76 // original connection (except for breaking the transactions). The 77 // caller should Raze() or otherwise cleanup as appropriate. 78 // 79 // TODO(shess): Later versions of SQLite allow extracting the path 80 // from the connection. 81 // TODO(shess): Allow specifying the connection point? 82 static scoped_ptr<Recovery> Begin( 83 Connection* connection, 84 const base::FilePath& db_path) WARN_UNUSED_RESULT; 85 86 // Mark recovery completed by replicating the recovery database over 87 // the original database, then closing the recovery database. The 88 // original database handle is poisoned, causing future calls 89 // against it to fail. 90 // 91 // If Recovered() is not called, the destructor will call 92 // Unrecoverable(). 93 // 94 // TODO(shess): At this time, this function can fail while leaving 95 // the original database intact. Figure out which failure cases 96 // should go to RazeAndClose() instead. 97 static bool Recovered(scoped_ptr<Recovery> r) WARN_UNUSED_RESULT; 98 99 // Indicate that the database is unrecoverable. The original 100 // database is razed, and the handle poisoned. 101 static void Unrecoverable(scoped_ptr<Recovery> r); 102 103 // When initially developing recovery code, sometimes the possible 104 // database states are not well-understood without further 105 // diagnostics. Abandon recovery but do not raze the original 106 // database. 107 // NOTE(shess): Only call this when adding recovery support. In the 108 // steady state, all databases should progress to recovered or razed. 109 static void Rollback(scoped_ptr<Recovery> r); 110 111 // Handle to the temporary recovery database. db()112 sql::Connection* db() { return &recover_db_; } 113 114 // Attempt to recover the named table from the corrupt database into 115 // the recovery database using a temporary recover virtual table. 116 // The virtual table schema is derived from the named table's schema 117 // in database [main]. Data is copied using INSERT OR REPLACE, so 118 // duplicates overwrite each other. 119 // 120 // |extend_columns| allows recovering tables which have excess 121 // columns relative to the target schema. The recover virtual table 122 // treats more data than specified as a sign of corruption. 123 // 124 // Returns true if all operations succeeded, with the number of rows 125 // recovered in |*rows_recovered|. 126 // 127 // NOTE(shess): Due to a flaw in the recovery virtual table, at this 128 // time this code injects the DEFAULT value of the target table in 129 // locations where the recovery table returns NULL. This is not 130 // entirely correct, because it happens both when there is a short 131 // row (correct) but also where there is an actual NULL value 132 // (incorrect). 133 // 134 // TODO(shess): Flag for INSERT OR REPLACE vs IGNORE. 135 // TODO(shess): Handle extended table names. 136 bool AutoRecoverTable(const char* table_name, 137 size_t extend_columns, 138 size_t* rows_recovered); 139 140 // Setup a recover virtual table at temp.recover_meta, reading from 141 // corrupt.meta. Returns true if created. 142 // TODO(shess): Perhaps integrate into Begin(). 143 // TODO(shess): Add helpers to fetch additional items from the meta 144 // table as needed. 145 bool SetupMeta(); 146 147 // Fetch the version number from temp.recover_meta. Returns false 148 // if the query fails, or if there is no version row. Otherwise 149 // returns true, with the version in |*version_number|. 150 // 151 // Only valid to call after successful SetupMeta(). 152 bool GetMetaVersionNumber(int* version_number); 153 154 private: 155 explicit Recovery(Connection* connection); 156 157 // Setup the recovery database handle for Begin(). Returns false in 158 // case anything failed. 159 bool Init(const base::FilePath& db_path) WARN_UNUSED_RESULT; 160 161 // Copy the recovered database over the original database. 162 bool Backup() WARN_UNUSED_RESULT; 163 164 // Close the recovery database, and poison the original handle. 165 // |raze| controls whether the original database is razed or just 166 // poisoned. 167 enum Disposition { 168 RAZE_AND_POISON, 169 POISON, 170 }; 171 void Shutdown(Disposition raze); 172 173 Connection* db_; // Original database connection. 174 Connection recover_db_; // Recovery connection. 175 176 DISALLOW_COPY_AND_ASSIGN(Recovery); 177 }; 178 179 } // namespace sql 180 181 #endif // SQL_RECOVERY_H_ 182