// 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 "base/basictypes.h"
#include "sql/connection.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.
//
// {
// scoped_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(r.Pass());
// return;
// }
//
// // Recover data in "mytable".
// size_t rows_recovered = 0;
// if (!r.AutoRecoverTable("mytable", 0, &rows_recovered)) {
// sql::Recovery::Unrecoverable(r.Pass());
// return;
// }
//
// // Manually cleanup additional constraints.
// if (!r.db()->Execute(kCleanupSql)) {
// sql::Recovery::Unrecoverable(r.Pass());
// return;
// }
//
// // Commit the recovered data to the original database file.
// sql::Recovery::Recovered(r.Pass());
// }
// }
//
// If Recovered() is not called, then RazeAndClose() is called on
// orig_db.
class SQL_EXPORT Recovery {
public:
~Recovery();
// This module is intended to be used in concert with a virtual
// table module (see third_party/sqlite/src/src/recover.c). If the
// build defines USE_SYSTEM_SQLITE, this module will not be present.
// TODO(shess): I am still debating how to handle this - perhaps it
// will just imply Unrecoverable(). This is exposed to allow tests
// to adapt to the cases, please do not rely on it in production
// code.
static bool FullRecoverySupported();
// 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 |connection| are rolled back.
//
// Returns NULL in case of failure, with no cleanup done on the
// original connection (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 connection.
// TODO(shess): Allow specifying the connection point?
static scoped_ptr<Recovery> Begin(
Connection* connection,
const base::FilePath& db_path) WARN_UNUSED_RESULT;
// 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.
static bool Recovered(scoped_ptr<Recovery> r) WARN_UNUSED_RESULT;
// Indicate that the database is unrecoverable. The original
// database is razed, and the handle poisoned.
static void Unrecoverable(scoped_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(scoped_ptr<Recovery> r);
// Handle to the temporary recovery database.
sql::Connection* 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 REPLACE, so
// duplicates overwrite each other.
//
// |extend_columns| allows recovering tables which have excess
// columns relative to the target schema. The recover virtual table
// treats more data than specified as a sign of corruption.
//
// 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 NULL. 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 extend_columns,
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);
private:
explicit Recovery(Connection* connection);
// Setup the recovery database handle for Begin(). Returns false in
// case anything failed.
bool Init(const base::FilePath& db_path) WARN_UNUSED_RESULT;
// Copy the recovered database over the original database.
bool Backup() WARN_UNUSED_RESULT;
// 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);
Connection* db_; // Original database connection.
Connection recover_db_; // Recovery connection.
DISALLOW_COPY_AND_ASSIGN(Recovery);
};
} // namespace sql
#endif // SQL_RECOVERY_H_