Feat: runtime profile override with getenv
[platform/framework/web/chromium-efl.git] / sql / recovery.h
1 // Copyright 2013 The Chromium Authors
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 <stddef.h>
9
10 #include <memory>
11
12 #include "base/component_export.h"
13 #include "base/memory/raw_ptr.h"
14 #include "sql/database.h"
15 #include "sql/internal_api_token.h"
16 #include "sql/sqlite_result_code_values.h"
17
18 namespace base {
19 struct Feature;
20 class FilePath;
21 }
22
23 namespace sql {
24
25 // WARNING: This API is still experimental. See https://crbug.com/1385500.
26 //
27 // Uses SQLite's built-in corruption recovery module to recover the database.
28 // See https://www.sqlite.org/recovery.html
29 //
30 // For now, feature teams should use only the `RecoverIfPossible()` method -
31 // which falls back to the legacy `sql::Recovery` below if necessary - in lieu
32 // of calling `RecoverDatabase()` directly.
33 class COMPONENT_EXPORT(SQL) BuiltInRecovery {
34  public:
35   enum class Strategy {
36     // Razes the database if it could not be recovered.
37     kRecoverOrRaze,
38
39     // Razes the database if it could not be recovered, or if a valid meta table
40     // with a version value could not be determined from the recovered database.
41     // Use this strategy if your client makes assertions about the version of
42     // the database schema.
43     kRecoverWithMetaVersionOrRaze,
44
45     // TODO(https://crbug.com/1385500): Consider exposing a way to keep around a
46     // successfully-recovered, but unsuccessfully-restored database if needed.
47   };
48
49   // These values are persisted to logs. Entries should not be renumbered
50   // and numeric values should never be reused.
51   enum class Result {
52     // Outcome not yet known. This value should never be logged.
53     kUnknown = 0,
54
55     // Successfully completed the full database recovery process.
56     kSuccess = 1,
57
58     // Failed to initialize and configure the sqlite3_recover object.
59     kFailedRecoveryInit = 2,
60
61     // Failed to run recovery with the sqlite3_recover object.
62     kFailedRecoveryRun = 3,
63
64     // The database was successfully recovered to a backup, but we could not
65     // open the newly-recovered database in order to copy it to the original
66     // database.
67     kFailedToOpenRecoveredDatabase = 4,
68
69     // The database was successfully recovered to a backup, but a meta table
70     // could not be found in the recovered database.
71     // Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
72     kFailedMetaTableDoesNotExist = 5,
73
74     // The database was successfully recovered to a backup, but the meta table
75     // could not be initialized.
76     // Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
77     kFailedMetaTableInit = 6,
78
79     // The database was successfully recovered to a backup, but a valid
80     // (meaning, positive) version number could not be read from the meta table.
81     // Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
82     kFailedMetaTableVersionWasInvalid = 7,
83
84     // Failed to initialize and configure the sqlite3_backup object.
85     kFailedBackupInit = 8,
86
87     // Failed to run backup with the sqlite3_backup object.
88     kFailedBackupRun = 9,
89     kMaxValue = kFailedBackupRun,
90   };
91
92   [[nodiscard]] static bool IsSupported();
93
94   // Returns true if `RecoverDatabase()` can plausibly fix `database` given this
95   // `extended_error`. This does not guarantee that `RecoverDatabase()` will
96   // successfully recover the database.
97   //
98   // Note that even if this method returns true, the database's error callback
99   // must be reset before recovery can be attempted.
100   [[nodiscard]] static bool ShouldAttemptRecovery(Database* database,
101                                                   int extended_error);
102
103   // WARNING: This API is experimental. For now, please use
104   // `RecoverIfPossible()` below rather than using this method directly.
105   //
106   // Attempts to recover `database`, and razes the database if it could not be
107   // recovered according to `strategy`. After attempting recovery, the database
108   // can be re-opened and assumed to be free of corruption.
109   //
110   // `database_uma_name` is used to log UMA specific to the given database.
111   //
112   // It is not considered an error if some or all of the data cannot be
113   // recovered due to database corruption, so it is possible that some records
114   // could not be salvaged from the corrupted database.
115   // TODO(https://crbug.com/1385500): Support the lost-and-found table if the
116   // need arises to try to restore all these records.
117   //
118   // It is illegal to attempt recovery if:
119   //   - `database` is null,
120   //   - `database` is not open,
121   //   - `database` is an in-memory or temporary database, or
122   //   - `database` has an error callback set
123   //
124   // During the recovery process, `database` is poisoned so that operations on
125   // the stack do not accidentally disrupt the restored data.
126   //
127   // Returns a SQLite error code specifying whether the database was
128   // successfully recovered.
129   [[nodiscard]] static SqliteResultCode RecoverDatabase(
130       Database* database,
131       Strategy strategy,
132       std::string database_uma_name = std::string());
133
134   // Similar to `RecoverDatabase()` above, but with a few key differences:
135   //   - Uses `BuiltInRecovery` or the legacy `Recovery` to recover the
136   //     database, as appropriate. This method facilitates the migration to the
137   //     newer recovery module with minimal impact on feature teams. The
138   //     expectation is that `Recovery` will eventually be removed entirely.
139   //     See https://crbug.com/1385500.
140   //   - Can be called without first checking `ShouldAttemptRecovery()`.
141   //   - `database`'s error callback will be reset if recovery is attempted.
142   //   - Must only be called from within a database error callback.
143   //   - Includes the option to pass a per-database feature flag indicating
144   //     whether `BuiltInRecovery` should be used to recover this database, if
145   //     it's supported. A per-database UMA may optionally be logged, as well.
146   //
147   // Recommended usage from within a database error callback:
148   //
149   //  // Attempt to recover the database, if recovery is possible.
150   //  if (sql::BuiltInRecovery::RecoverIfPossible(
151   //          &db, extended_error,
152   //          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
153   //          &features::kMyFeatureTeamShouldUseBuiltInRecoveryIfSupported,
154   //          "MyFeatureDatabase")) {
155   //    // Recovery was attempted. The database handle has been poisoned and the
156   //    // error callback has been reset.
157   //
158   //    // ...
159   //  }
160   //
161   [[nodiscard]] static bool RecoverIfPossible(
162       Database* database,
163       int extended_error,
164       Strategy strategy,
165       const base::Feature* const use_builtin_recovery_if_supported_flag =
166           nullptr,
167       std::string database_uma_name = std::string());
168
169   BuiltInRecovery(const BuiltInRecovery&) = delete;
170   BuiltInRecovery& operator=(const BuiltInRecovery&) = delete;
171
172  private:
173   BuiltInRecovery(Database* database,
174                   Strategy strategy,
175                   std::string database_uma_name);
176   ~BuiltInRecovery();
177
178   // Entry point.
179   SqliteResultCode RecoverAndReplaceDatabase();
180
181   // Use SQLite's corruption recovery module to store the recovered content in
182   // `recover_db_`. See https://www.sqlite.org/recovery.html
183   SqliteResultCode AttemptToRecoverDatabaseToBackup();
184
185   bool RecoveredDbHasValidMetaTable();
186
187   // Use SQLite's Online Backup API to replace the original database with
188   // `recover_db_`. See https://www.sqlite.org/backup.html
189   SqliteResultCode ReplaceOriginalWithRecoveredDb();
190
191   void SetRecoverySucceeded();
192   void SetRecoveryFailed(Result failure_result, SqliteResultCode result_code);
193
194   const Strategy strategy_;
195
196   // If non-empty, UMA will be logged with the result of the recovery for this
197   // specific database.
198   const std::string database_uma_name_;
199
200   // Result of the recovery. This value must be set to something other than
201   // `kUnknown` before this object is destroyed.
202   Result result_ = Result::kUnknown;
203   SqliteResultCode sqlite_result_code_ = SqliteResultCode::kOk;
204
205   raw_ptr<Database> db_;  // Original Database connection.
206   Database recover_db_;   // Recovery Database connection.
207
208   base::FilePath recovery_database_path_;
209 };
210
211 // Recovery module for sql/.  The basic idea is to create a fresh database and
212 // populate it with the recovered contents of the original database.  If
213 // recovery is successful, the recovered database is backed up over the original
214 // database.  If recovery is not successful, the original database is razed.  In
215 // either case, the original handle is poisoned so that operations on the stack
216 // do not accidentally disrupt the restored data.
217 //
218 // RecoverDatabase() automates this, including recoverying the schema of from
219 // the suspect database.  If a database requires special handling, such as
220 // recovering between different schema, or tables requiring post-processing,
221 // then the module can be used manually like:
222 //
223 // {
224 //   std::unique_ptr<sql::Recovery> r =
225 //       sql::Recovery::Begin(orig_db, orig_db_path);
226 //   if (r) {
227 //     // Create the schema to recover to.  On failure, clear the
228 //     // database.
229 //     if (!r.db()->Execute(kCreateSchemaSql)) {
230 //       sql::Recovery::Unrecoverable(std::move(r));
231 //       return;
232 //     }
233 //
234 //     // Recover data in "mytable".
235 //     size_t rows_recovered = 0;
236 //     if (!r.AutoRecoverTable("mytable", 0, &rows_recovered)) {
237 //       sql::Recovery::Unrecoverable(std::move(r));
238 //       return;
239 //     }
240 //
241 //     // Manually cleanup additional constraints.
242 //     if (!r.db()->Execute(kCleanupSql)) {
243 //       sql::Recovery::Unrecoverable(std::move(r));
244 //       return;
245 //     }
246 //
247 //     // Commit the recovered data to the original database file.
248 //     sql::Recovery::Recovered(std::move(r));
249 //   }
250 // }
251 //
252 // If Recovered() is not called, then RazeAndPoison() is called on
253 // orig_db.
254 class COMPONENT_EXPORT(SQL) Recovery {
255  public:
256   Recovery(const Recovery&) = delete;
257   Recovery& operator=(const Recovery&) = delete;
258   ~Recovery();
259
260   // Begin the recovery process by opening a temporary database handle
261   // and attach the existing database to it at "corrupt".  To prevent
262   // deadlock, all transactions on |database| are rolled back.
263   //
264   // Returns nullptr in case of failure, with no cleanup done on the
265   // original database (except for breaking the transactions).  The
266   // caller should Raze() or otherwise cleanup as appropriate.
267   //
268   // TODO(shess): Later versions of SQLite allow extracting the path
269   // from the database.
270   // TODO(shess): Allow specifying the connection point?
271   [[nodiscard]] static std::unique_ptr<Recovery> Begin(
272       Database* database,
273       const base::FilePath& db_path);
274
275   // Mark recovery completed by replicating the recovery database over
276   // the original database, then closing the recovery database.  The
277   // original database handle is poisoned, causing future calls
278   // against it to fail.
279   //
280   // If Recovered() is not called, the destructor will call
281   // Unrecoverable().
282   //
283   // TODO(shess): At this time, this function can fail while leaving
284   // the original database intact.  Figure out which failure cases
285   // should go to RazeAndPoison() instead.
286   [[nodiscard]] static bool Recovered(std::unique_ptr<Recovery> r);
287
288   // Indicate that the database is unrecoverable.  The original
289   // database is razed, and the handle poisoned.
290   static void Unrecoverable(std::unique_ptr<Recovery> r);
291
292   // When initially developing recovery code, sometimes the possible
293   // database states are not well-understood without further
294   // diagnostics.  Abandon recovery but do not raze the original
295   // database.
296   // NOTE(shess): Only call this when adding recovery support.  In the
297   // steady state, all databases should progress to recovered or razed.
298   static void Rollback(std::unique_ptr<Recovery> r);
299
300   // Handle to the temporary recovery database.
301   sql::Database* db() { return &recover_db_; }
302
303   // Attempt to recover the named table from the corrupt database into
304   // the recovery database using a temporary recover virtual table.
305   // The virtual table schema is derived from the named table's schema
306   // in database [main].  Data is copied using INSERT OR IGNORE, so
307   // duplicates are dropped.
308   //
309   // If the source table has fewer columns than the target, the target
310   // DEFAULT value will be used for those columns.
311   //
312   // Returns true if all operations succeeded, with the number of rows
313   // recovered in |*rows_recovered|.
314   //
315   // NOTE(shess): Due to a flaw in the recovery virtual table, at this
316   // time this code injects the DEFAULT value of the target table in
317   // locations where the recovery table returns nullptr.  This is not
318   // entirely correct, because it happens both when there is a short
319   // row (correct) but also where there is an actual NULL value
320   // (incorrect).
321   //
322   // TODO(shess): Flag for INSERT OR REPLACE vs IGNORE.
323   // TODO(shess): Handle extended table names.
324   bool AutoRecoverTable(const char* table_name, size_t* rows_recovered);
325
326   // Setup a recover virtual table at temp.recover_meta, reading from
327   // corrupt.meta.  Returns true if created.
328   // TODO(shess): Perhaps integrate into Begin().
329   // TODO(shess): Add helpers to fetch additional items from the meta
330   // table as needed.
331   bool SetupMeta();
332
333   // Fetch the version number from temp.recover_meta.  Returns false
334   // if the query fails, or if there is no version row.  Otherwise
335   // returns true, with the version in |*version_number|.
336   //
337   // Only valid to call after successful SetupMeta().
338   bool GetMetaVersionNumber(int* version_number);
339
340   // Attempt to recover the database by creating a new database with schema from
341   // |db|, then copying over as much data as possible.  If successful, the
342   // recovery handle is returned to allow the caller to make additional changes,
343   // such as validating constraints not expressed in the schema.
344   //
345   // In case of SQLITE_NOTADB, the database is deemed unrecoverable and deleted.
346   [[nodiscard]] static std::unique_ptr<Recovery> BeginRecoverDatabase(
347       Database* db,
348       const base::FilePath& db_path);
349
350   // Call BeginRecoverDatabase() to recover the database, then commit the
351   // changes using Recovered().  After this call, the |db| handle will be
352   // poisoned (though technically remaining open) so that future calls will
353   // return errors until the handle is re-opened.
354   static void RecoverDatabase(Database* db, const base::FilePath& db_path);
355
356   // Variant on RecoverDatabase() which requires that the database have a valid
357   // meta table with a version value.  The meta version value is used by some
358   // clients to make assertions about the database schema.  If this information
359   // cannot be determined, the database is considered unrecoverable.
360   static void RecoverDatabaseWithMetaVersion(Database* db,
361                                              const base::FilePath& db_path);
362
363   // Returns true for SQLite errors which RecoverDatabase() can plausibly fix.
364   // This does not guarantee that RecoverDatabase() will successfully recover
365   // the database.
366   static bool ShouldRecover(int extended_error);
367
368   // Enables the "recover" SQLite extension for a database connection.
369   //
370   // Returns a SQLite error code.
371   static int EnableRecoveryExtension(Database* db, InternalApiToken);
372
373  private:
374   explicit Recovery(Database* database);
375
376   // Setup the recovery database handle for Begin().  Returns false in
377   // case anything failed.
378   [[nodiscard]] bool Init(const base::FilePath& db_path);
379
380   // Copy the recovered database over the original database.
381   [[nodiscard]] bool Backup();
382
383   // Close the recovery database, and poison the original handle.
384   // |raze| controls whether the original database is razed or just
385   // poisoned.
386   enum Disposition {
387     RAZE_AND_POISON,
388     POISON,
389   };
390   void Shutdown(Disposition raze);
391
392   raw_ptr<Database> db_;  // Original Database connection.
393   Database recover_db_;  // Recovery Database connection.
394 };
395
396 }  // namespace sql
397
398 #endif  // SQL_RECOVERY_H_