blob: 60fb6747b83c640891f01c933c9e1463a2c3dd76 [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
8#include "base/basictypes.h"
9
10#include "sql/connection.h"
11
12namespace base {
13class FilePath;
14}
15
16namespace 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) {
[email protected]ae4f1622013-12-08 06:49:1230// // 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;
[email protected]8d409412013-07-19 18:25:3035// }
[email protected]ae4f1622013-12-08 06:49:1236//
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());
[email protected]8d409412013-07-19 18:25:3052// }
53// }
54//
55// If Recovered() is not called, then RazeAndClose() is called on
56// orig_db.
57
58class SQL_EXPORT Recovery {
59 public:
60 ~Recovery();
61
[email protected]df5d95c42013-10-07 22:24:2262 // 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
[email protected]8d409412013-07-19 18:25:3071 // 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 //
[email protected]74cdede2013-09-25 05:39:5794 // TODO(shess): At this time, this function can fail while leaving
[email protected]8d409412013-07-19 18:25:3095 // 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
[email protected]74cdede2013-09-25 05:39:57103 // 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
[email protected]8d409412013-07-19 18:25:30111 // Handle to the temporary recovery database.
112 sql::Connection* db() { return &recover_db_; }
113
[email protected]a8848a72013-11-18 04:18:47114 // 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
[email protected]8d409412013-07-19 18:25:30154 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_