Home | History | Annotate | Download | only in sql
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #ifndef SQL_RECOVERY_H_
      6 #define SQL_RECOVERY_H_
      7 
      8 #include "base/basictypes.h"
      9 
     10 #include "sql/connection.h"
     11 
     12 namespace base {
     13 class FilePath;
     14 }
     15 
     16 namespace sql {
     17 
     18 // Recovery module for sql/.  The basic idea is to create a fresh
     19 // database and populate it with the recovered contents of the
     20 // original database.  If recovery is successful, the recovered
     21 // database is backed up over the original database.  If recovery is
     22 // not successful, the original database is razed.  In either case,
     23 // the original handle is poisoned so that operations on the stack do
     24 // not accidentally disrupt the restored data.
     25 //
     26 // {
     27 //   scoped_ptr<sql::Recovery> r =
     28 //       sql::Recovery::Begin(orig_db, orig_db_path);
     29 //   if (r) {
     30 //     // Create the schema to recover to.  On failure, clear the
     31 //     // database.
     32 //     if (!r.db()->Execute(kCreateSchemaSql)) {
     33 //       sql::Recovery::Unrecoverable(r.Pass());
     34 //       return;
     35 //     }
     36 //
     37 //     // Recover data in "mytable".
     38 //     size_t rows_recovered = 0;
     39 //     if (!r.AutoRecoverTable("mytable", 0, &rows_recovered)) {
     40 //       sql::Recovery::Unrecoverable(r.Pass());
     41 //       return;
     42 //     }
     43 //
     44 //     // Manually cleanup additional constraints.
     45 //     if (!r.db()->Execute(kCleanupSql)) {
     46 //       sql::Recovery::Unrecoverable(r.Pass());
     47 //       return;
     48 //     }
     49 //
     50 //     // Commit the recovered data to the original database file.
     51 //     sql::Recovery::Recovered(r.Pass());
     52 //   }
     53 // }
     54 //
     55 // If Recovered() is not called, then RazeAndClose() is called on
     56 // orig_db.
     57 
     58 class SQL_EXPORT Recovery {
     59  public:
     60   ~Recovery();
     61 
     62   // This module is intended to be used in concert with a virtual
     63   // table module (see third_party/sqlite/src/src/recover.c).  If the
     64   // build defines USE_SYSTEM_SQLITE, this module will not be present.
     65   // TODO(shess): I am still debating how to handle this - perhaps it
     66   // will just imply Unrecoverable().  This is exposed to allow tests
     67   // to adapt to the cases, please do not rely on it in production
     68   // code.
     69   static bool FullRecoverySupported();
     70 
     71   // Begin the recovery process by opening a temporary database handle
     72   // and attach the existing database to it at "corrupt".  To prevent
     73   // deadlock, all transactions on |connection| are rolled back.
     74   //
     75   // Returns NULL in case of failure, with no cleanup done on the
     76   // original connection (except for breaking the transactions).  The
     77   // caller should Raze() or otherwise cleanup as appropriate.
     78   //
     79   // TODO(shess): Later versions of SQLite allow extracting the path
     80   // from the connection.
     81   // TODO(shess): Allow specifying the connection point?
     82   static scoped_ptr<Recovery> Begin(
     83       Connection* connection,
     84       const base::FilePath& db_path) WARN_UNUSED_RESULT;
     85 
     86   // Mark recovery completed by replicating the recovery database over
     87   // the original database, then closing the recovery database.  The
     88   // original database handle is poisoned, causing future calls
     89   // against it to fail.
     90   //
     91   // If Recovered() is not called, the destructor will call
     92   // Unrecoverable().
     93   //
     94   // TODO(shess): At this time, this function can fail while leaving
     95   // the original database intact.  Figure out which failure cases
     96   // should go to RazeAndClose() instead.
     97   static bool Recovered(scoped_ptr<Recovery> r) WARN_UNUSED_RESULT;
     98 
     99   // Indicate that the database is unrecoverable.  The original
    100   // database is razed, and the handle poisoned.
    101   static void Unrecoverable(scoped_ptr<Recovery> r);
    102 
    103   // When initially developing recovery code, sometimes the possible
    104   // database states are not well-understood without further
    105   // diagnostics.  Abandon recovery but do not raze the original
    106   // database.
    107   // NOTE(shess): Only call this when adding recovery support.  In the
    108   // steady state, all databases should progress to recovered or razed.
    109   static void Rollback(scoped_ptr<Recovery> r);
    110 
    111   // Handle to the temporary recovery database.
    112   sql::Connection* db() { return &recover_db_; }
    113 
    114   // Attempt to recover the named table from the corrupt database into
    115   // the recovery database using a temporary recover virtual table.
    116   // The virtual table schema is derived from the named table's schema
    117   // in database [main].  Data is copied using INSERT OR REPLACE, so
    118   // duplicates overwrite each other.
    119   //
    120   // |extend_columns| allows recovering tables which have excess
    121   // columns relative to the target schema.  The recover virtual table
    122   // treats more data than specified as a sign of corruption.
    123   //
    124   // Returns true if all operations succeeded, with the number of rows
    125   // recovered in |*rows_recovered|.
    126   //
    127   // NOTE(shess): Due to a flaw in the recovery virtual table, at this
    128   // time this code injects the DEFAULT value of the target table in
    129   // locations where the recovery table returns NULL.  This is not
    130   // entirely correct, because it happens both when there is a short
    131   // row (correct) but also where there is an actual NULL value
    132   // (incorrect).
    133   //
    134   // TODO(shess): Flag for INSERT OR REPLACE vs IGNORE.
    135   // TODO(shess): Handle extended table names.
    136   bool AutoRecoverTable(const char* table_name,
    137                         size_t extend_columns,
    138                         size_t* rows_recovered);
    139 
    140   // Setup a recover virtual table at temp.recover_meta, reading from
    141   // corrupt.meta.  Returns true if created.
    142   // TODO(shess): Perhaps integrate into Begin().
    143   // TODO(shess): Add helpers to fetch additional items from the meta
    144   // table as needed.
    145   bool SetupMeta();
    146 
    147   // Fetch the version number from temp.recover_meta.  Returns false
    148   // if the query fails, or if there is no version row.  Otherwise
    149   // returns true, with the version in |*version_number|.
    150   //
    151   // Only valid to call after successful SetupMeta().
    152   bool GetMetaVersionNumber(int* version_number);
    153 
    154  private:
    155   explicit Recovery(Connection* connection);
    156 
    157   // Setup the recovery database handle for Begin().  Returns false in
    158   // case anything failed.
    159   bool Init(const base::FilePath& db_path) WARN_UNUSED_RESULT;
    160 
    161   // Copy the recovered database over the original database.
    162   bool Backup() WARN_UNUSED_RESULT;
    163 
    164   // Close the recovery database, and poison the original handle.
    165   // |raze| controls whether the original database is razed or just
    166   // poisoned.
    167   enum Disposition {
    168     RAZE_AND_POISON,
    169     POISON,
    170   };
    171   void Shutdown(Disposition raze);
    172 
    173   Connection* db_;         // Original database connection.
    174   Connection recover_db_;  // Recovery connection.
    175 
    176   DISALLOW_COPY_AND_ASSIGN(Recovery);
    177 };
    178 
    179 }  // namespace sql
    180 
    181 #endif  // SQL_RECOVERY_H_
    182