[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 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 | |
avi | 0b51920 | 2015-12-21 07:25:19 | [diff] [blame] | 8 | #include <stddef.h> |
| 9 | |
mostynb | d82cd995 | 2016-04-11 20:05:34 | [diff] [blame] | 10 | #include <memory> |
| 11 | |
tfarina | 720d4f3 | 2015-05-11 22:31:26 | [diff] [blame] | 12 | #include "base/macros.h" |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 13 | #include "sql/connection.h" |
| 14 | |
| 15 | namespace base { |
| 16 | class FilePath; |
| 17 | } |
| 18 | |
| 19 | namespace sql { |
| 20 | |
shess | a402e75 | 2016-07-02 00:25:11 | [diff] [blame] | 21 | // Recovery module for sql/. The basic idea is to create a fresh database and |
| 22 | // populate it with the recovered contents of the original database. If |
| 23 | // recovery is successful, the recovered database is backed up over the original |
| 24 | // database. If recovery is not successful, the original database is razed. In |
| 25 | // either case, the original handle is poisoned so that operations on the stack |
| 26 | // do not accidentally disrupt the restored data. |
| 27 | // |
twifkak | 7c48428 | 2017-03-03 23:05:02 | [diff] [blame] | 28 | // RecoverDatabase() automates this, including recoverying the schema of from |
| 29 | // the suspect database. If a database requires special handling, such as |
shess | a402e75 | 2016-07-02 00:25:11 | [diff] [blame] | 30 | // recovering between different schema, or tables requiring post-processing, |
| 31 | // then the module can be used manually like: |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 32 | // |
| 33 | // { |
mostynb | d82cd995 | 2016-04-11 20:05:34 | [diff] [blame] | 34 | // std::unique_ptr<sql::Recovery> r = |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 35 | // sql::Recovery::Begin(orig_db, orig_db_path); |
| 36 | // if (r) { |
[email protected] | ae4f162 | 2013-12-08 06:49:12 | [diff] [blame] | 37 | // // Create the schema to recover to. On failure, clear the |
| 38 | // // database. |
| 39 | // if (!r.db()->Execute(kCreateSchemaSql)) { |
dcheng | 7061e5f | 2016-03-04 01:21:47 | [diff] [blame] | 40 | // sql::Recovery::Unrecoverable(std::move(r)); |
[email protected] | ae4f162 | 2013-12-08 06:49:12 | [diff] [blame] | 41 | // return; |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 42 | // } |
[email protected] | ae4f162 | 2013-12-08 06:49:12 | [diff] [blame] | 43 | // |
| 44 | // // Recover data in "mytable". |
| 45 | // size_t rows_recovered = 0; |
| 46 | // if (!r.AutoRecoverTable("mytable", 0, &rows_recovered)) { |
dcheng | 7061e5f | 2016-03-04 01:21:47 | [diff] [blame] | 47 | // sql::Recovery::Unrecoverable(std::move(r)); |
[email protected] | ae4f162 | 2013-12-08 06:49:12 | [diff] [blame] | 48 | // return; |
| 49 | // } |
| 50 | // |
| 51 | // // Manually cleanup additional constraints. |
| 52 | // if (!r.db()->Execute(kCleanupSql)) { |
dcheng | 7061e5f | 2016-03-04 01:21:47 | [diff] [blame] | 53 | // sql::Recovery::Unrecoverable(std::move(r)); |
[email protected] | ae4f162 | 2013-12-08 06:49:12 | [diff] [blame] | 54 | // return; |
| 55 | // } |
| 56 | // |
| 57 | // // Commit the recovered data to the original database file. |
dcheng | 7061e5f | 2016-03-04 01:21:47 | [diff] [blame] | 58 | // sql::Recovery::Recovered(std::move(r)); |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 59 | // } |
| 60 | // } |
| 61 | // |
| 62 | // If Recovered() is not called, then RazeAndClose() is called on |
| 63 | // orig_db. |
| 64 | |
| 65 | class SQL_EXPORT Recovery { |
| 66 | public: |
| 67 | ~Recovery(); |
| 68 | |
| 69 | // Begin the recovery process by opening a temporary database handle |
| 70 | // and attach the existing database to it at "corrupt". To prevent |
| 71 | // deadlock, all transactions on |connection| are rolled back. |
| 72 | // |
| 73 | // Returns NULL in case of failure, with no cleanup done on the |
| 74 | // original connection (except for breaking the transactions). The |
| 75 | // caller should Raze() or otherwise cleanup as appropriate. |
| 76 | // |
| 77 | // TODO(shess): Later versions of SQLite allow extracting the path |
| 78 | // from the connection. |
| 79 | // TODO(shess): Allow specifying the connection point? |
mostynb | d82cd995 | 2016-04-11 20:05:34 | [diff] [blame] | 80 | static std::unique_ptr<Recovery> Begin(Connection* connection, |
| 81 | const base::FilePath& db_path) |
| 82 | WARN_UNUSED_RESULT; |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 83 | |
| 84 | // Mark recovery completed by replicating the recovery database over |
| 85 | // the original database, then closing the recovery database. The |
| 86 | // original database handle is poisoned, causing future calls |
| 87 | // against it to fail. |
| 88 | // |
| 89 | // If Recovered() is not called, the destructor will call |
| 90 | // Unrecoverable(). |
| 91 | // |
[email protected] | 74cdede | 2013-09-25 05:39:57 | [diff] [blame] | 92 | // TODO(shess): At this time, this function can fail while leaving |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 93 | // the original database intact. Figure out which failure cases |
| 94 | // should go to RazeAndClose() instead. |
mostynb | d82cd995 | 2016-04-11 20:05:34 | [diff] [blame] | 95 | static bool Recovered(std::unique_ptr<Recovery> r) WARN_UNUSED_RESULT; |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 96 | |
| 97 | // Indicate that the database is unrecoverable. The original |
| 98 | // database is razed, and the handle poisoned. |
mostynb | d82cd995 | 2016-04-11 20:05:34 | [diff] [blame] | 99 | static void Unrecoverable(std::unique_ptr<Recovery> r); |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 100 | |
[email protected] | 74cdede | 2013-09-25 05:39:57 | [diff] [blame] | 101 | // When initially developing recovery code, sometimes the possible |
| 102 | // database states are not well-understood without further |
| 103 | // diagnostics. Abandon recovery but do not raze the original |
| 104 | // database. |
| 105 | // NOTE(shess): Only call this when adding recovery support. In the |
| 106 | // steady state, all databases should progress to recovered or razed. |
mostynb | d82cd995 | 2016-04-11 20:05:34 | [diff] [blame] | 107 | static void Rollback(std::unique_ptr<Recovery> r); |
[email protected] | 74cdede | 2013-09-25 05:39:57 | [diff] [blame] | 108 | |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 109 | // Handle to the temporary recovery database. |
| 110 | sql::Connection* db() { return &recover_db_; } |
| 111 | |
[email protected] | a8848a7 | 2013-11-18 04:18:47 | [diff] [blame] | 112 | // Attempt to recover the named table from the corrupt database into |
| 113 | // the recovery database using a temporary recover virtual table. |
| 114 | // The virtual table schema is derived from the named table's schema |
shess | 806f499 | 2016-02-04 21:12:09 | [diff] [blame] | 115 | // in database [main]. Data is copied using INSERT OR IGNORE, so |
| 116 | // duplicates are dropped. |
[email protected] | a8848a7 | 2013-11-18 04:18:47 | [diff] [blame] | 117 | // |
shess | 6f68bd3 | 2016-02-04 19:29:44 | [diff] [blame] | 118 | // If the source table has fewer columns than the target, the target |
| 119 | // DEFAULT value will be used for those columns. |
[email protected] | a8848a7 | 2013-11-18 04:18:47 | [diff] [blame] | 120 | // |
| 121 | // Returns true if all operations succeeded, with the number of rows |
| 122 | // recovered in |*rows_recovered|. |
| 123 | // |
| 124 | // NOTE(shess): Due to a flaw in the recovery virtual table, at this |
| 125 | // time this code injects the DEFAULT value of the target table in |
| 126 | // locations where the recovery table returns NULL. This is not |
| 127 | // entirely correct, because it happens both when there is a short |
| 128 | // row (correct) but also where there is an actual NULL value |
| 129 | // (incorrect). |
| 130 | // |
| 131 | // TODO(shess): Flag for INSERT OR REPLACE vs IGNORE. |
| 132 | // TODO(shess): Handle extended table names. |
shess | 6f68bd3 | 2016-02-04 19:29:44 | [diff] [blame] | 133 | bool AutoRecoverTable(const char* table_name, size_t* rows_recovered); |
[email protected] | a8848a7 | 2013-11-18 04:18:47 | [diff] [blame] | 134 | |
| 135 | // Setup a recover virtual table at temp.recover_meta, reading from |
| 136 | // corrupt.meta. Returns true if created. |
| 137 | // TODO(shess): Perhaps integrate into Begin(). |
| 138 | // TODO(shess): Add helpers to fetch additional items from the meta |
| 139 | // table as needed. |
| 140 | bool SetupMeta(); |
| 141 | |
| 142 | // Fetch the version number from temp.recover_meta. Returns false |
| 143 | // if the query fails, or if there is no version row. Otherwise |
| 144 | // returns true, with the version in |*version_number|. |
| 145 | // |
| 146 | // Only valid to call after successful SetupMeta(). |
| 147 | bool GetMetaVersionNumber(int* version_number); |
| 148 | |
shess | a402e75 | 2016-07-02 00:25:11 | [diff] [blame] | 149 | // Attempt to recover the database by creating a new database with schema from |
shess | 5207e145 | 2017-04-12 23:55:33 | [diff] [blame] | 150 | // |db|, then copying over as much data as possible. If successful, the |
| 151 | // recovery handle is returned to allow the caller to make additional changes, |
| 152 | // such as validating constraints not expressed in the schema. |
shess | 00d65d4 | 2017-03-02 21:12:19 | [diff] [blame] | 153 | // |
| 154 | // In case of SQLITE_NOTADB, the database is deemed unrecoverable and deleted. |
shess | 5207e145 | 2017-04-12 23:55:33 | [diff] [blame] | 155 | static std::unique_ptr<Recovery> BeginRecoverDatabase( |
| 156 | Connection* db, |
| 157 | const base::FilePath& db_path) WARN_UNUSED_RESULT; |
| 158 | |
| 159 | // Call BeginRecoverDatabase() to recover the database, then commit the |
| 160 | // changes using Recovered(). After this call, the |db| handle will be |
| 161 | // poisoned (though technically remaining open) so that future calls will |
| 162 | // return errors until the handle is re-opened. |
shess | a402e75 | 2016-07-02 00:25:11 | [diff] [blame] | 163 | static void RecoverDatabase(Connection* db, const base::FilePath& db_path); |
| 164 | |
shess | 5207e145 | 2017-04-12 23:55:33 | [diff] [blame] | 165 | // Variant on RecoverDatabase() which requires that the database have a valid |
| 166 | // meta table with a version value. The meta version value is used by some |
| 167 | // clients to make assertions about the database schema. If this information |
| 168 | // cannot be determined, the database is considered unrecoverable. |
| 169 | static void RecoverDatabaseWithMetaVersion(Connection* db, |
| 170 | const base::FilePath& db_path); |
| 171 | |
shess | a402e75 | 2016-07-02 00:25:11 | [diff] [blame] | 172 | // Returns true for SQLite errors which RecoverDatabase() can plausibly fix. |
| 173 | // This does not guarantee that RecoverDatabase() will successfully recover |
| 174 | // the database. |
| 175 | static bool ShouldRecover(int extended_error); |
| 176 | |
[email protected] | 8d40941 | 2013-07-19 18:25:30 | [diff] [blame] | 177 | private: |
| 178 | explicit Recovery(Connection* connection); |
| 179 | |
| 180 | // Setup the recovery database handle for Begin(). Returns false in |
| 181 | // case anything failed. |
| 182 | bool Init(const base::FilePath& db_path) WARN_UNUSED_RESULT; |
| 183 | |
| 184 | // Copy the recovered database over the original database. |
| 185 | bool Backup() WARN_UNUSED_RESULT; |
| 186 | |
| 187 | // Close the recovery database, and poison the original handle. |
| 188 | // |raze| controls whether the original database is razed or just |
| 189 | // poisoned. |
| 190 | enum Disposition { |
| 191 | RAZE_AND_POISON, |
| 192 | POISON, |
| 193 | }; |
| 194 | void Shutdown(Disposition raze); |
| 195 | |
| 196 | Connection* db_; // Original database connection. |
| 197 | Connection recover_db_; // Recovery connection. |
| 198 | |
| 199 | DISALLOW_COPY_AND_ASSIGN(Recovery); |
| 200 | }; |
| 201 | |
| 202 | } // namespace sql |
| 203 | |
| 204 | #endif // SQL_RECOVERY_H_ |