1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SQL_RECOVERY_H_
#define SQL_RECOVERY_H_
#include <stddef.h>
#include <string>
#include "base/component_export.h"
#include "base/memory/raw_ptr.h"
#include "sql/database.h"
#include "sql/internal_api_token.h"
#include "sql/sqlite_result_code_values.h"
namespace base {
class FilePath;
}
namespace sql {
// Recovery module for sql/. Please see the `RecoverIfPossible()` method for how
// to use this class.
//
// This module is capable of recovering databases which the legacy recovery
// module could not recover. These include:
// - tables with the WITHOUT ROWID optimization
// - databases which use Write-Ahead Log (i.e. WAL mode)
// - NOTE: as WAL mode is still experimental (see https://crbug.com/1416213)
// recovery should not be attempted on WAL databases for now.
//
// Uses SQLite's recovery extension: https://www.sqlite.org/recovery.html
class COMPONENT_EXPORT(SQL) Recovery {
public:
enum class Strategy {
// Razes the database if it could not be recovered.
kRecoverOrRaze,
// Razes the database if it could not be recovered, or if a valid meta table
// with a version value could not be determined from the recovered database.
// Use this strategy if your client makes assertions about the version of
// the database schema.
kRecoverWithMetaVersionOrRaze,
// TODO(crbug.com/40061775): Consider exposing a way to keep around a
// successfully-recovered, but unsuccessfully-restored database if needed.
};
// These values are persisted to logs. Entries should not be renumbered
// and numeric values should never be reused.
enum class Result {
// Outcome not yet known. This value should never be logged.
kUnknown = 0,
// Successfully completed the full database recovery process.
kSuccess = 1,
// Failed to initialize and configure the sqlite3_recover object.
kFailedRecoveryInit = 2,
// Failed to run recovery with the sqlite3_recover object.
kFailedRecoveryRun = 3,
// The database was successfully recovered to a backup, but we could not
// open the newly-recovered database in order to copy it to the original
// database.
kFailedToOpenRecoveredDatabase = 4,
// The database was successfully recovered to a backup, but a meta table
// could not be found in the recovered database.
// Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
kFailedMetaTableDoesNotExist = 5,
// The database was successfully recovered to a backup, but the meta table
// could not be initialized.
// Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
kFailedMetaTableInit = 6,
// The database was successfully recovered to a backup, but a valid
// (meaning, positive) version number could not be read from the meta table.
// Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
kFailedMetaTableVersionWasInvalid = 7,
// Failed to initialize and configure the sqlite3_backup object.
kFailedBackupInit = 8,
// Failed to run backup with the sqlite3_backup object.
kFailedBackupRun = 9,
kMaxValue = kFailedBackupRun,
};
// Returns true if `RecoverDatabase()` can plausibly fix `database` given this
// `extended_error`. This does not guarantee that `RecoverDatabase()` will
// successfully recover the database.
//
// Note that even if this method returns true, the database's error callback
// must be reset before recovery can be attempted.
[[nodiscard]] static bool ShouldAttemptRecovery(Database* database,
int extended_error);
// Use `RecoverIfPossible()` below rather than using this method directly.
//
// Attempts to recover `database`, and razes the database if it could not be
// recovered according to `strategy`. After attempting recovery, the database
// can be re-opened and assumed to be free of corruption.
//
// Use Database::set_histogram_tag() to log UMA for recovery results specific
// to the given feature database.
//
// It is not considered an error if some or all of the data cannot be
// recovered due to database corruption, so it is possible that some records
// could not be salvaged from the corrupted database.
// TODO(crbug.com/40061775): Support the lost-and-found table if the
// need arises to try to restore all these records.
//
// It is illegal to attempt recovery if:
// - `database` is null,
// - `database` is not open,
// - `database` is an in-memory or temporary database, or
// - `database` has an error callback set
//
// During the recovery process, `database` is poisoned so that operations on
// the stack do not accidentally disrupt the restored data.
//
// Returns a SQLite error code specifying whether the database was
// successfully recovered.
[[nodiscard]] static SqliteResultCode RecoverDatabase(Database* database,
Strategy strategy);
// Similar to `RecoverDatabase()` above, but with a couple key differences:
// - Can be called without first checking `ShouldAttemptRecovery()`.
// - `database`'s error callback will be reset if recovery is attempted.
// - Must only be called from within a database error callback.
//
// Recommended usage from within a database error callback:
//
// // Attempt to recover the database, if recovery is possible.
// if (sql::Recovery::RecoverIfPossible(
// &db, extended_error,
// sql::Recovery::Strategy::kRecoverWithMetaVersionOrRaze)) {
// // Recovery was attempted. The database handle has been poisoned and the
// // error callback has been reset.
//
// // ...
// }
//
[[nodiscard]] static bool RecoverIfPossible(Database* database,
int extended_error,
Strategy strategy);
Recovery(const Recovery&) = delete;
Recovery& operator=(const Recovery&) = delete;
private:
Recovery(Database* database, Strategy strategy);
~Recovery();
// Entry point.
SqliteResultCode RecoverAndReplaceDatabase();
// Use SQLite's corruption recovery module to store the recovered content in
// `recover_db_`. See https://www.sqlite.org/recovery.html
SqliteResultCode AttemptToRecoverDatabaseToBackup();
bool RecoveredDbHasValidMetaTable();
// Use SQLite's Online Backup API to replace the original database with
// `recover_db_`. See https://www.sqlite.org/backup.html
SqliteResultCode ReplaceOriginalWithRecoveredDb();
void SetRecoverySucceeded();
void SetRecoveryFailed(Result failure_result, SqliteResultCode result_code);
const Strategy strategy_;
// If non-empty, UMA will be logged with the result of the recovery for this
// specific database.
std::string database_uma_name_;
// Result of the recovery. This value must be set to something other than
// `kUnknown` before this object is destroyed.
Result result_ = Result::kUnknown;
SqliteResultCode sqlite_result_code_ = SqliteResultCode::kOk;
raw_ptr<Database> db_; // Original Database connection.
Database recover_db_; // Recovery Database connection.
base::FilePath recovery_database_path_;
};
} // namespace sql
#endif // SQL_RECOVERY_H_
|