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