• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
db()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