recovery.h 6.57 KB
Newer Older
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
// 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) {
30 31 32 33 34
//     // Create the schema to recover to.  On failure, clear the
//     // database.
//     if (!r.db()->Execute(kCreateSchemaSql)) {
//       sql::Recovery::Unrecoverable(r.Pass());
//       return;
35
//     }
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
//
//     // 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());
52 53 54 55 56 57 58 59 60 61
//   }
// }
//
// If Recovered() is not called, then RazeAndClose() is called on
// orig_db.

class SQL_EXPORT Recovery {
 public:
  ~Recovery();

62 63 64 65 66 67 68 69 70
  // 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();

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
  // 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().
  //
94
  // TODO(shess): At this time, this function can fail while leaving
95 96 97 98 99 100 101 102
  // 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);

103 104 105 106 107 108 109 110
  // 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);

111 112 113
  // Handle to the temporary recovery database.
  sql::Connection* db() { return &recover_db_; }

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
  // 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);

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
 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_