summaryrefslogtreecommitdiff
path: root/chromium/sql/recovery.h
blob: 60fb6747b83c640891f01c933c9e1463a2c3dd76 (plain)
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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
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
// 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_