1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #ifndef SQL_RECOVERY_H_
6 #define SQL_RECOVERY_H_
12 #include "base/component_export.h"
13 #include "base/memory/raw_ptr.h"
14 #include "sql/database.h"
15 #include "sql/internal_api_token.h"
16 #include "sql/sqlite_result_code_values.h"
25 // WARNING: This API is still experimental. See https://crbug.com/1385500.
27 // Uses SQLite's built-in corruption recovery module to recover the database.
28 // See https://www.sqlite.org/recovery.html
30 // For now, feature teams should use only the `RecoverIfPossible()` method -
31 // which falls back to the legacy `sql::Recovery` below if necessary - in lieu
32 // of calling `RecoverDatabase()` directly.
33 class COMPONENT_EXPORT(SQL) BuiltInRecovery {
36 // Razes the database if it could not be recovered.
39 // Razes the database if it could not be recovered, or if a valid meta table
40 // with a version value could not be determined from the recovered database.
41 // Use this strategy if your client makes assertions about the version of
42 // the database schema.
43 kRecoverWithMetaVersionOrRaze,
45 // TODO(https://crbug.com/1385500): Consider exposing a way to keep around a
46 // successfully-recovered, but unsuccessfully-restored database if needed.
49 // These values are persisted to logs. Entries should not be renumbered
50 // and numeric values should never be reused.
52 // Outcome not yet known. This value should never be logged.
55 // Successfully completed the full database recovery process.
58 // Failed to initialize and configure the sqlite3_recover object.
59 kFailedRecoveryInit = 2,
61 // Failed to run recovery with the sqlite3_recover object.
62 kFailedRecoveryRun = 3,
64 // The database was successfully recovered to a backup, but we could not
65 // open the newly-recovered database in order to copy it to the original
67 kFailedToOpenRecoveredDatabase = 4,
69 // The database was successfully recovered to a backup, but a meta table
70 // could not be found in the recovered database.
71 // Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
72 kFailedMetaTableDoesNotExist = 5,
74 // The database was successfully recovered to a backup, but the meta table
75 // could not be initialized.
76 // Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
77 kFailedMetaTableInit = 6,
79 // The database was successfully recovered to a backup, but a valid
80 // (meaning, positive) version number could not be read from the meta table.
81 // Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
82 kFailedMetaTableVersionWasInvalid = 7,
84 // Failed to initialize and configure the sqlite3_backup object.
85 kFailedBackupInit = 8,
87 // Failed to run backup with the sqlite3_backup object.
89 kMaxValue = kFailedBackupRun,
92 [[nodiscard]] static bool IsSupported();
94 // Returns true if `RecoverDatabase()` can plausibly fix `database` given this
95 // `extended_error`. This does not guarantee that `RecoverDatabase()` will
96 // successfully recover the database.
98 // Note that even if this method returns true, the database's error callback
99 // must be reset before recovery can be attempted.
100 [[nodiscard]] static bool ShouldAttemptRecovery(Database* database,
103 // WARNING: This API is experimental. For now, please use
104 // `RecoverIfPossible()` below rather than using this method directly.
106 // Attempts to recover `database`, and razes the database if it could not be
107 // recovered according to `strategy`. After attempting recovery, the database
108 // can be re-opened and assumed to be free of corruption.
110 // `database_uma_name` is used to log UMA specific to the given database.
112 // It is not considered an error if some or all of the data cannot be
113 // recovered due to database corruption, so it is possible that some records
114 // could not be salvaged from the corrupted database.
115 // TODO(https://crbug.com/1385500): Support the lost-and-found table if the
116 // need arises to try to restore all these records.
118 // It is illegal to attempt recovery if:
119 // - `database` is null,
120 // - `database` is not open,
121 // - `database` is an in-memory or temporary database, or
122 // - `database` has an error callback set
124 // During the recovery process, `database` is poisoned so that operations on
125 // the stack do not accidentally disrupt the restored data.
127 // Returns a SQLite error code specifying whether the database was
128 // successfully recovered.
129 [[nodiscard]] static SqliteResultCode RecoverDatabase(
132 std::string database_uma_name = std::string());
134 // Similar to `RecoverDatabase()` above, but with a few key differences:
135 // - Uses `BuiltInRecovery` or the legacy `Recovery` to recover the
136 // database, as appropriate. This method facilitates the migration to the
137 // newer recovery module with minimal impact on feature teams. The
138 // expectation is that `Recovery` will eventually be removed entirely.
139 // See https://crbug.com/1385500.
140 // - Can be called without first checking `ShouldAttemptRecovery()`.
141 // - `database`'s error callback will be reset if recovery is attempted.
142 // - Must only be called from within a database error callback.
143 // - Includes the option to pass a per-database feature flag indicating
144 // whether `BuiltInRecovery` should be used to recover this database, if
145 // it's supported. A per-database UMA may optionally be logged, as well.
147 // Recommended usage from within a database error callback:
149 // // Attempt to recover the database, if recovery is possible.
150 // if (sql::BuiltInRecovery::RecoverIfPossible(
151 // &db, extended_error,
152 // sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
153 // &features::kMyFeatureTeamShouldUseBuiltInRecoveryIfSupported,
154 // "MyFeatureDatabase")) {
155 // // Recovery was attempted. The database handle has been poisoned and the
156 // // error callback has been reset.
161 [[nodiscard]] static bool RecoverIfPossible(
165 const base::Feature* const use_builtin_recovery_if_supported_flag =
167 std::string database_uma_name = std::string());
169 BuiltInRecovery(const BuiltInRecovery&) = delete;
170 BuiltInRecovery& operator=(const BuiltInRecovery&) = delete;
173 BuiltInRecovery(Database* database,
175 std::string database_uma_name);
179 SqliteResultCode RecoverAndReplaceDatabase();
181 // Use SQLite's corruption recovery module to store the recovered content in
182 // `recover_db_`. See https://www.sqlite.org/recovery.html
183 SqliteResultCode AttemptToRecoverDatabaseToBackup();
185 bool RecoveredDbHasValidMetaTable();
187 // Use SQLite's Online Backup API to replace the original database with
188 // `recover_db_`. See https://www.sqlite.org/backup.html
189 SqliteResultCode ReplaceOriginalWithRecoveredDb();
191 void SetRecoverySucceeded();
192 void SetRecoveryFailed(Result failure_result, SqliteResultCode result_code);
194 const Strategy strategy_;
196 // If non-empty, UMA will be logged with the result of the recovery for this
197 // specific database.
198 const std::string database_uma_name_;
200 // Result of the recovery. This value must be set to something other than
201 // `kUnknown` before this object is destroyed.
202 Result result_ = Result::kUnknown;
203 SqliteResultCode sqlite_result_code_ = SqliteResultCode::kOk;
205 raw_ptr<Database> db_; // Original Database connection.
206 Database recover_db_; // Recovery Database connection.
208 base::FilePath recovery_database_path_;
211 // Recovery module for sql/. The basic idea is to create a fresh database and
212 // populate it with the recovered contents of the original database. If
213 // recovery is successful, the recovered database is backed up over the original
214 // database. If recovery is not successful, the original database is razed. In
215 // either case, the original handle is poisoned so that operations on the stack
216 // do not accidentally disrupt the restored data.
218 // RecoverDatabase() automates this, including recoverying the schema of from
219 // the suspect database. If a database requires special handling, such as
220 // recovering between different schema, or tables requiring post-processing,
221 // then the module can be used manually like:
224 // std::unique_ptr<sql::Recovery> r =
225 // sql::Recovery::Begin(orig_db, orig_db_path);
227 // // Create the schema to recover to. On failure, clear the
229 // if (!r.db()->Execute(kCreateSchemaSql)) {
230 // sql::Recovery::Unrecoverable(std::move(r));
234 // // Recover data in "mytable".
235 // size_t rows_recovered = 0;
236 // if (!r.AutoRecoverTable("mytable", 0, &rows_recovered)) {
237 // sql::Recovery::Unrecoverable(std::move(r));
241 // // Manually cleanup additional constraints.
242 // if (!r.db()->Execute(kCleanupSql)) {
243 // sql::Recovery::Unrecoverable(std::move(r));
247 // // Commit the recovered data to the original database file.
248 // sql::Recovery::Recovered(std::move(r));
252 // If Recovered() is not called, then RazeAndPoison() is called on
254 class COMPONENT_EXPORT(SQL) Recovery {
256 Recovery(const Recovery&) = delete;
257 Recovery& operator=(const Recovery&) = delete;
260 // Begin the recovery process by opening a temporary database handle
261 // and attach the existing database to it at "corrupt". To prevent
262 // deadlock, all transactions on |database| are rolled back.
264 // Returns nullptr in case of failure, with no cleanup done on the
265 // original database (except for breaking the transactions). The
266 // caller should Raze() or otherwise cleanup as appropriate.
268 // TODO(shess): Later versions of SQLite allow extracting the path
269 // from the database.
270 // TODO(shess): Allow specifying the connection point?
271 [[nodiscard]] static std::unique_ptr<Recovery> Begin(
273 const base::FilePath& db_path);
275 // Mark recovery completed by replicating the recovery database over
276 // the original database, then closing the recovery database. The
277 // original database handle is poisoned, causing future calls
278 // against it to fail.
280 // If Recovered() is not called, the destructor will call
283 // TODO(shess): At this time, this function can fail while leaving
284 // the original database intact. Figure out which failure cases
285 // should go to RazeAndPoison() instead.
286 [[nodiscard]] static bool Recovered(std::unique_ptr<Recovery> r);
288 // Indicate that the database is unrecoverable. The original
289 // database is razed, and the handle poisoned.
290 static void Unrecoverable(std::unique_ptr<Recovery> r);
292 // When initially developing recovery code, sometimes the possible
293 // database states are not well-understood without further
294 // diagnostics. Abandon recovery but do not raze the original
296 // NOTE(shess): Only call this when adding recovery support. In the
297 // steady state, all databases should progress to recovered or razed.
298 static void Rollback(std::unique_ptr<Recovery> r);
300 // Handle to the temporary recovery database.
301 sql::Database* db() { return &recover_db_; }
303 // Attempt to recover the named table from the corrupt database into
304 // the recovery database using a temporary recover virtual table.
305 // The virtual table schema is derived from the named table's schema
306 // in database [main]. Data is copied using INSERT OR IGNORE, so
307 // duplicates are dropped.
309 // If the source table has fewer columns than the target, the target
310 // DEFAULT value will be used for those columns.
312 // Returns true if all operations succeeded, with the number of rows
313 // recovered in |*rows_recovered|.
315 // NOTE(shess): Due to a flaw in the recovery virtual table, at this
316 // time this code injects the DEFAULT value of the target table in
317 // locations where the recovery table returns nullptr. This is not
318 // entirely correct, because it happens both when there is a short
319 // row (correct) but also where there is an actual NULL value
322 // TODO(shess): Flag for INSERT OR REPLACE vs IGNORE.
323 // TODO(shess): Handle extended table names.
324 bool AutoRecoverTable(const char* table_name, size_t* rows_recovered);
326 // Setup a recover virtual table at temp.recover_meta, reading from
327 // corrupt.meta. Returns true if created.
328 // TODO(shess): Perhaps integrate into Begin().
329 // TODO(shess): Add helpers to fetch additional items from the meta
333 // Fetch the version number from temp.recover_meta. Returns false
334 // if the query fails, or if there is no version row. Otherwise
335 // returns true, with the version in |*version_number|.
337 // Only valid to call after successful SetupMeta().
338 bool GetMetaVersionNumber(int* version_number);
340 // Attempt to recover the database by creating a new database with schema from
341 // |db|, then copying over as much data as possible. If successful, the
342 // recovery handle is returned to allow the caller to make additional changes,
343 // such as validating constraints not expressed in the schema.
345 // In case of SQLITE_NOTADB, the database is deemed unrecoverable and deleted.
346 [[nodiscard]] static std::unique_ptr<Recovery> BeginRecoverDatabase(
348 const base::FilePath& db_path);
350 // Call BeginRecoverDatabase() to recover the database, then commit the
351 // changes using Recovered(). After this call, the |db| handle will be
352 // poisoned (though technically remaining open) so that future calls will
353 // return errors until the handle is re-opened.
354 static void RecoverDatabase(Database* db, const base::FilePath& db_path);
356 // Variant on RecoverDatabase() which requires that the database have a valid
357 // meta table with a version value. The meta version value is used by some
358 // clients to make assertions about the database schema. If this information
359 // cannot be determined, the database is considered unrecoverable.
360 static void RecoverDatabaseWithMetaVersion(Database* db,
361 const base::FilePath& db_path);
363 // Returns true for SQLite errors which RecoverDatabase() can plausibly fix.
364 // This does not guarantee that RecoverDatabase() will successfully recover
366 static bool ShouldRecover(int extended_error);
368 // Enables the "recover" SQLite extension for a database connection.
370 // Returns a SQLite error code.
371 static int EnableRecoveryExtension(Database* db, InternalApiToken);
374 explicit Recovery(Database* database);
376 // Setup the recovery database handle for Begin(). Returns false in
377 // case anything failed.
378 [[nodiscard]] bool Init(const base::FilePath& db_path);
380 // Copy the recovered database over the original database.
381 [[nodiscard]] bool Backup();
383 // Close the recovery database, and poison the original handle.
384 // |raze| controls whether the original database is razed or just
390 void Shutdown(Disposition raze);
392 raw_ptr<Database> db_; // Original Database connection.
393 Database recover_db_; // Recovery Database connection.
398 #endif // SQL_RECOVERY_H_