123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- // Copyright 2013 The Chromium Authors. All rights reserved.
- // 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 <memory>
- #include "base/component_export.h"
- #include "base/memory/raw_ptr.h"
- #include "sql/database.h"
- #include "sql/internal_api_token.h"
- namespace base {
- class FilePath;
- }
- namespace sql {
- // Recovery module for sql/. The basic idea is to create a fresh database and
- // populate it with the recovered contents of the original database. If
- // recovery is successful, the recovered database is backed up over the original
- // database. If recovery is not successful, the original database is razed. In
- // either case, the original handle is poisoned so that operations on the stack
- // do not accidentally disrupt the restored data.
- //
- // RecoverDatabase() automates this, including recoverying the schema of from
- // the suspect database. If a database requires special handling, such as
- // recovering between different schema, or tables requiring post-processing,
- // then the module can be used manually like:
- //
- // {
- // std::unique_ptr<sql::Recovery> r =
- // sql::Recovery::Begin(orig_db, orig_db_path);
- // if (r) {
- // // Create the schema to recover to. On failure, clear the
- // // database.
- // if (!r.db()->Execute(kCreateSchemaSql)) {
- // sql::Recovery::Unrecoverable(std::move(r));
- // return;
- // }
- //
- // // Recover data in "mytable".
- // size_t rows_recovered = 0;
- // if (!r.AutoRecoverTable("mytable", 0, &rows_recovered)) {
- // sql::Recovery::Unrecoverable(std::move(r));
- // return;
- // }
- //
- // // Manually cleanup additional constraints.
- // if (!r.db()->Execute(kCleanupSql)) {
- // sql::Recovery::Unrecoverable(std::move(r));
- // return;
- // }
- //
- // // Commit the recovered data to the original database file.
- // sql::Recovery::Recovered(std::move(r));
- // }
- // }
- //
- // If Recovered() is not called, then RazeAndClose() is called on
- // orig_db.
- class COMPONENT_EXPORT(SQL) Recovery {
- public:
- Recovery(const Recovery&) = delete;
- Recovery& operator=(const Recovery&) = delete;
- ~Recovery();
- // Begin the recovery process by opening a temporary database handle
- // and attach the existing database to it at "corrupt". To prevent
- // deadlock, all transactions on |database| are rolled back.
- //
- // Returns nullptr in case of failure, with no cleanup done on the
- // original database (except for breaking the transactions). The
- // caller should Raze() or otherwise cleanup as appropriate.
- //
- // TODO(shess): Later versions of SQLite allow extracting the path
- // from the database.
- // TODO(shess): Allow specifying the connection point?
- [[nodiscard]] static std::unique_ptr<Recovery> Begin(
- Database* database,
- const base::FilePath& db_path);
- // Mark recovery completed by replicating the recovery database over
- // the original database, then closing the recovery database. The
- // original database handle is poisoned, causing future calls
- // against it to fail.
- //
- // If Recovered() is not called, the destructor will call
- // Unrecoverable().
- //
- // TODO(shess): At this time, this function can fail while leaving
- // the original database intact. Figure out which failure cases
- // should go to RazeAndClose() instead.
- [[nodiscard]] static bool Recovered(std::unique_ptr<Recovery> r);
- // Indicate that the database is unrecoverable. The original
- // database is razed, and the handle poisoned.
- static void Unrecoverable(std::unique_ptr<Recovery> r);
- // When initially developing recovery code, sometimes the possible
- // database states are not well-understood without further
- // diagnostics. Abandon recovery but do not raze the original
- // database.
- // NOTE(shess): Only call this when adding recovery support. In the
- // steady state, all databases should progress to recovered or razed.
- static void Rollback(std::unique_ptr<Recovery> r);
- // Handle to the temporary recovery database.
- sql::Database* db() { return &recover_db_; }
- // Attempt to recover the named table from the corrupt database into
- // the recovery database using a temporary recover virtual table.
- // The virtual table schema is derived from the named table's schema
- // in database [main]. Data is copied using INSERT OR IGNORE, so
- // duplicates are dropped.
- //
- // If the source table has fewer columns than the target, the target
- // DEFAULT value will be used for those columns.
- //
- // Returns true if all operations succeeded, with the number of rows
- // recovered in |*rows_recovered|.
- //
- // NOTE(shess): Due to a flaw in the recovery virtual table, at this
- // time this code injects the DEFAULT value of the target table in
- // locations where the recovery table returns nullptr. This is not
- // entirely correct, because it happens both when there is a short
- // row (correct) but also where there is an actual NULL value
- // (incorrect).
- //
- // TODO(shess): Flag for INSERT OR REPLACE vs IGNORE.
- // TODO(shess): Handle extended table names.
- bool AutoRecoverTable(const char* table_name, size_t* rows_recovered);
- // Setup a recover virtual table at temp.recover_meta, reading from
- // corrupt.meta. Returns true if created.
- // TODO(shess): Perhaps integrate into Begin().
- // TODO(shess): Add helpers to fetch additional items from the meta
- // table as needed.
- bool SetupMeta();
- // Fetch the version number from temp.recover_meta. Returns false
- // if the query fails, or if there is no version row. Otherwise
- // returns true, with the version in |*version_number|.
- //
- // Only valid to call after successful SetupMeta().
- bool GetMetaVersionNumber(int* version_number);
- // Attempt to recover the database by creating a new database with schema from
- // |db|, then copying over as much data as possible. If successful, the
- // recovery handle is returned to allow the caller to make additional changes,
- // such as validating constraints not expressed in the schema.
- //
- // In case of SQLITE_NOTADB, the database is deemed unrecoverable and deleted.
- [[nodiscard]] static std::unique_ptr<Recovery> BeginRecoverDatabase(
- Database* db,
- const base::FilePath& db_path);
- // Call BeginRecoverDatabase() to recover the database, then commit the
- // changes using Recovered(). After this call, the |db| handle will be
- // poisoned (though technically remaining open) so that future calls will
- // return errors until the handle is re-opened.
- static void RecoverDatabase(Database* db, const base::FilePath& db_path);
- // Variant on RecoverDatabase() which requires that the database have a valid
- // meta table with a version value. The meta version value is used by some
- // clients to make assertions about the database schema. If this information
- // cannot be determined, the database is considered unrecoverable.
- static void RecoverDatabaseWithMetaVersion(Database* db,
- const base::FilePath& db_path);
- // Returns true for SQLite errors which RecoverDatabase() can plausibly fix.
- // This does not guarantee that RecoverDatabase() will successfully recover
- // the database.
- static bool ShouldRecover(int extended_error);
- // Enables the "recover" SQLite extension for a database connection.
- //
- // Returns a SQLite error code.
- static int EnableRecoveryExtension(Database* db, InternalApiToken);
- private:
- explicit Recovery(Database* database);
- // Setup the recovery database handle for Begin(). Returns false in
- // case anything failed.
- [[nodiscard]] bool Init(const base::FilePath& db_path);
- // Copy the recovered database over the original database.
- [[nodiscard]] bool Backup();
- // Close the recovery database, and poison the original handle.
- // |raze| controls whether the original database is razed or just
- // poisoned.
- enum Disposition {
- RAZE_AND_POISON,
- POISON,
- };
- void Shutdown(Disposition raze);
- raw_ptr<Database> db_; // Original Database connection.
- Database recover_db_; // Recovery Database connection.
- };
- } // namespace sql
- #endif // SQL_RECOVERY_H_
|