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
12namespace base {
13class FilePath;
14}
15
16namespace 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
58class 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