blob: 7c4b7168f5458b9766932728c6fb44c9554dd82d [file] [log] [blame]
[email protected]8d409412013-07-19 18:25:301// 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
avi0b519202015-12-21 07:25:198#include <stddef.h>
9
mostynbd82cd9952016-04-11 20:05:3410#include <memory>
11
tfarina720d4f32015-05-11 22:31:2612#include "base/macros.h"
[email protected]8d409412013-07-19 18:25:3013#include "sql/connection.h"
14
15namespace base {
16class FilePath;
17}
18
19namespace sql {
20
shessa402e752016-07-02 00:25:1121// 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//
twifkak7c484282017-03-03 23:05:0228// RecoverDatabase() automates this, including recoverying the schema of from
29// the suspect database. If a database requires special handling, such as
shessa402e752016-07-02 00:25:1130// recovering between different schema, or tables requiring post-processing,
31// then the module can be used manually like:
[email protected]8d409412013-07-19 18:25:3032//
33// {
mostynbd82cd9952016-04-11 20:05:3434// std::unique_ptr<sql::Recovery> r =
[email protected]8d409412013-07-19 18:25:3035// sql::Recovery::Begin(orig_db, orig_db_path);
36// if (r) {
[email protected]ae4f1622013-12-08 06:49:1237// // Create the schema to recover to. On failure, clear the
38// // database.
39// if (!r.db()->Execute(kCreateSchemaSql)) {
dcheng7061e5f2016-03-04 01:21:4740// sql::Recovery::Unrecoverable(std::move(r));
[email protected]ae4f1622013-12-08 06:49:1241// return;
[email protected]8d409412013-07-19 18:25:3042// }
[email protected]ae4f1622013-12-08 06:49:1243//
44// // Recover data in "mytable".
45// size_t rows_recovered = 0;
46// if (!r.AutoRecoverTable("mytable", 0, &rows_recovered)) {
dcheng7061e5f2016-03-04 01:21:4747// sql::Recovery::Unrecoverable(std::move(r));
[email protected]ae4f1622013-12-08 06:49:1248// return;
49// }
50//
51// // Manually cleanup additional constraints.
52// if (!r.db()->Execute(kCleanupSql)) {
dcheng7061e5f2016-03-04 01:21:4753// sql::Recovery::Unrecoverable(std::move(r));
[email protected]ae4f1622013-12-08 06:49:1254// return;
55// }
56//
57// // Commit the recovered data to the original database file.
dcheng7061e5f2016-03-04 01:21:4758// sql::Recovery::Recovered(std::move(r));
[email protected]8d409412013-07-19 18:25:3059// }
60// }
61//
62// If Recovered() is not called, then RazeAndClose() is called on
63// orig_db.
64
65class 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?
mostynbd82cd9952016-04-11 20:05:3480 static std::unique_ptr<Recovery> Begin(Connection* connection,
81 const base::FilePath& db_path)
82 WARN_UNUSED_RESULT;
[email protected]8d409412013-07-19 18:25:3083
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]74cdede2013-09-25 05:39:5792 // TODO(shess): At this time, this function can fail while leaving
[email protected]8d409412013-07-19 18:25:3093 // the original database intact. Figure out which failure cases
94 // should go to RazeAndClose() instead.
mostynbd82cd9952016-04-11 20:05:3495 static bool Recovered(std::unique_ptr<Recovery> r) WARN_UNUSED_RESULT;
[email protected]8d409412013-07-19 18:25:3096
97 // Indicate that the database is unrecoverable. The original
98 // database is razed, and the handle poisoned.
mostynbd82cd9952016-04-11 20:05:3499 static void Unrecoverable(std::unique_ptr<Recovery> r);
[email protected]8d409412013-07-19 18:25:30100
[email protected]74cdede2013-09-25 05:39:57101 // 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.
mostynbd82cd9952016-04-11 20:05:34107 static void Rollback(std::unique_ptr<Recovery> r);
[email protected]74cdede2013-09-25 05:39:57108
[email protected]8d409412013-07-19 18:25:30109 // Handle to the temporary recovery database.
110 sql::Connection* db() { return &recover_db_; }
111
[email protected]a8848a72013-11-18 04:18:47112 // 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
shess806f4992016-02-04 21:12:09115 // in database [main]. Data is copied using INSERT OR IGNORE, so
116 // duplicates are dropped.
[email protected]a8848a72013-11-18 04:18:47117 //
shess6f68bd32016-02-04 19:29:44118 // If the source table has fewer columns than the target, the target
119 // DEFAULT value will be used for those columns.
[email protected]a8848a72013-11-18 04:18:47120 //
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.
shess6f68bd32016-02-04 19:29:44133 bool AutoRecoverTable(const char* table_name, size_t* rows_recovered);
[email protected]a8848a72013-11-18 04:18:47134
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
shessa402e752016-07-02 00:25:11149 // Attempt to recover the database by creating a new database with schema from
shess5207e1452017-04-12 23:55:33150 // |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.
shess00d65d42017-03-02 21:12:19153 //
154 // In case of SQLITE_NOTADB, the database is deemed unrecoverable and deleted.
shess5207e1452017-04-12 23:55:33155 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.
shessa402e752016-07-02 00:25:11163 static void RecoverDatabase(Connection* db, const base::FilePath& db_path);
164
shess5207e1452017-04-12 23:55:33165 // 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
shessa402e752016-07-02 00:25:11172 // 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]8d409412013-07-19 18:25:30177 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_