blob: e4fdb7b189ebe0bbc9fc5aa73ae20b1e79d1a478 [file] [log] [blame]
[email protected]8d409412013-07-19 18:25:301// 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
dchenge48600452015-12-28 02:24:505#include "sql/recovery.h"
avi0b519202015-12-21 07:25:196
dchenge48600452015-12-28 02:24:507#include <stddef.h>
[email protected]ae4f1622013-12-08 06:49:128#include <string>
dchenge48600452015-12-28 02:24:509#include <utility>
[email protected]ae4f1622013-12-08 06:49:1210
[email protected]dd325f052013-08-06 02:37:4011#include "base/bind.h"
[email protected]ae4f1622013-12-08 06:49:1212#include "base/files/file_path.h"
thestig22dfc4012014-09-05 08:29:4413#include "base/files/file_util.h"
[email protected]8d409412013-07-19 18:25:3014#include "base/files/scoped_temp_dir.h"
[email protected]cfb821612014-07-10 00:48:0615#include "base/path_service.h"
[email protected]a8848a72013-11-18 04:18:4716#include "base/strings/string_number_conversions.h"
[email protected]8d409412013-07-19 18:25:3017#include "sql/connection.h"
18#include "sql/meta_table.h"
[email protected]8d409412013-07-19 18:25:3019#include "sql/statement.h"
[email protected]cfb821612014-07-10 00:48:0620#include "sql/test/paths.h"
[email protected]8d409412013-07-19 18:25:3021#include "sql/test/scoped_error_ignorer.h"
erg102ceb412015-06-20 01:38:1322#include "sql/test/sql_test_base.h"
[email protected]ae4f1622013-12-08 06:49:1223#include "sql/test/test_helpers.h"
[email protected]8d409412013-07-19 18:25:3024#include "testing/gtest/include/gtest/gtest.h"
25#include "third_party/sqlite/sqlite3.h"
26
27namespace {
28
29// Execute |sql|, and stringify the results with |column_sep| between
30// columns and |row_sep| between rows.
31// TODO(shess): Promote this to a central testing helper.
32std::string ExecuteWithResults(sql::Connection* db,
33 const char* sql,
34 const char* column_sep,
35 const char* row_sep) {
36 sql::Statement s(db->GetUniqueStatement(sql));
37 std::string ret;
38 while (s.Step()) {
39 if (!ret.empty())
40 ret += row_sep;
41 for (int i = 0; i < s.ColumnCount(); ++i) {
42 if (i > 0)
43 ret += column_sep;
[email protected]a8848a72013-11-18 04:18:4744 if (s.ColumnType(i) == sql::COLUMN_TYPE_NULL) {
45 ret += "<null>";
46 } else if (s.ColumnType(i) == sql::COLUMN_TYPE_BLOB) {
47 ret += "<x'";
48 ret += base::HexEncode(s.ColumnBlob(i), s.ColumnByteLength(i));
49 ret += "'>";
50 } else {
51 ret += s.ColumnString(i);
52 }
[email protected]8d409412013-07-19 18:25:3053 }
54 }
55 return ret;
56}
57
58// Dump consistent human-readable representation of the database
59// schema. For tables or indices, this will contain the sql command
60// to create the table or index. For certain automatic SQLite
61// structures with no sql, the name is used.
62std::string GetSchema(sql::Connection* db) {
63 const char kSql[] =
64 "SELECT COALESCE(sql, name) FROM sqlite_master ORDER BY 1";
65 return ExecuteWithResults(db, kSql, "|", "\n");
66}
67
erg102ceb412015-06-20 01:38:1368using SQLRecoveryTest = sql::SQLTestBase;
[email protected]8d409412013-07-19 18:25:3069
shess6ac39542016-02-04 20:56:2270// Baseline sql::Recovery test covering the different ways to dispose of the
71// scoped pointer received from sql::Recovery::Begin().
[email protected]8d409412013-07-19 18:25:3072TEST_F(SQLRecoveryTest, RecoverBasic) {
73 const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
74 const char kInsertSql[] = "INSERT INTO x VALUES ('This is a test')";
shess6ac39542016-02-04 20:56:2275 const char kAltInsertSql[] = "INSERT INTO x VALUES ('That was a test')";
[email protected]8d409412013-07-19 18:25:3076 ASSERT_TRUE(db().Execute(kCreateSql));
77 ASSERT_TRUE(db().Execute(kInsertSql));
78 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
79
80 // If the Recovery handle goes out of scope without being
81 // Recovered(), the database is razed.
82 {
83 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
84 ASSERT_TRUE(recovery.get());
85 }
86 EXPECT_FALSE(db().is_open());
87 ASSERT_TRUE(Reopen());
88 EXPECT_TRUE(db().is_open());
89 ASSERT_EQ("", GetSchema(&db()));
90
91 // Recreate the database.
92 ASSERT_TRUE(db().Execute(kCreateSql));
93 ASSERT_TRUE(db().Execute(kInsertSql));
94 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
95
96 // Unrecoverable() also razes.
97 {
98 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
99 ASSERT_TRUE(recovery.get());
dchenge48600452015-12-28 02:24:50100 sql::Recovery::Unrecoverable(std::move(recovery));
[email protected]8d409412013-07-19 18:25:30101
102 // TODO(shess): Test that calls to recover.db() start failing.
103 }
104 EXPECT_FALSE(db().is_open());
105 ASSERT_TRUE(Reopen());
106 EXPECT_TRUE(db().is_open());
107 ASSERT_EQ("", GetSchema(&db()));
108
shess874ea1bd2016-02-02 05:15:06109 // Attempting to recover a previously-recovered handle fails early.
110 {
111 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
112 ASSERT_TRUE(recovery.get());
113 recovery.reset();
114
115 recovery = sql::Recovery::Begin(&db(), db_path());
116 ASSERT_FALSE(recovery.get());
117 }
118 ASSERT_TRUE(Reopen());
119
[email protected]8d409412013-07-19 18:25:30120 // Recreate the database.
121 ASSERT_TRUE(db().Execute(kCreateSql));
122 ASSERT_TRUE(db().Execute(kInsertSql));
123 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
124
shess6ac39542016-02-04 20:56:22125 // Unrecovered table to distinguish from recovered database.
126 ASSERT_TRUE(db().Execute("CREATE TABLE y (c INTEGER)"));
127 ASSERT_NE("CREATE TABLE x (t TEXT)", GetSchema(&db()));
128
[email protected]8d409412013-07-19 18:25:30129 // Recovered() replaces the original with the "recovered" version.
130 {
131 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
132 ASSERT_TRUE(recovery.get());
133
134 // Create the new version of the table.
135 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
136
137 // Insert different data to distinguish from original database.
[email protected]8d409412013-07-19 18:25:30138 ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
139
140 // Successfully recovered.
dchenge48600452015-12-28 02:24:50141 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]8d409412013-07-19 18:25:30142 }
143 EXPECT_FALSE(db().is_open());
144 ASSERT_TRUE(Reopen());
145 EXPECT_TRUE(db().is_open());
146 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
147
148 const char* kXSql = "SELECT * FROM x ORDER BY 1";
[email protected]dd325f052013-08-06 02:37:40149 ASSERT_EQ("That was a test",
150 ExecuteWithResults(&db(), kXSql, "|", "\n"));
shess6ac39542016-02-04 20:56:22151
152 // Reset the database contents.
153 ASSERT_TRUE(db().Execute("DELETE FROM x"));
154 ASSERT_TRUE(db().Execute(kInsertSql));
155
156 // Rollback() discards recovery progress and leaves the database as it was.
157 {
158 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
159 ASSERT_TRUE(recovery.get());
160
161 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
162 ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
163
164 sql::Recovery::Rollback(std::move(recovery));
165 }
166 EXPECT_FALSE(db().is_open());
167 ASSERT_TRUE(Reopen());
168 EXPECT_TRUE(db().is_open());
169 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
170
171 ASSERT_EQ("This is a test",
172 ExecuteWithResults(&db(), kXSql, "|", "\n"));
[email protected]8d409412013-07-19 18:25:30173}
174
[email protected]dd325f052013-08-06 02:37:40175// The recovery virtual table is only supported for Chromium's SQLite.
176#if !defined(USE_SYSTEM_SQLITE)
177
shess6ac39542016-02-04 20:56:22178// Test operation of the virtual table used by sql::Recovery.
[email protected]dd325f052013-08-06 02:37:40179TEST_F(SQLRecoveryTest, VirtualTable) {
180 const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
181 ASSERT_TRUE(db().Execute(kCreateSql));
182 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test')"));
183 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test')"));
184
185 // Successfully recover the database.
186 {
187 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
188
189 // Tables to recover original DB, now at [corrupt].
190 const char kRecoveryCreateSql[] =
191 "CREATE VIRTUAL TABLE temp.recover_x using recover("
192 " corrupt.x,"
193 " t TEXT STRICT"
194 ")";
195 ASSERT_TRUE(recovery->db()->Execute(kRecoveryCreateSql));
196
197 // Re-create the original schema.
198 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
199
200 // Copy the data from the recovery tables to the new database.
201 const char kRecoveryCopySql[] =
202 "INSERT INTO x SELECT t FROM recover_x";
203 ASSERT_TRUE(recovery->db()->Execute(kRecoveryCopySql));
204
205 // Successfully recovered.
dchenge48600452015-12-28 02:24:50206 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]dd325f052013-08-06 02:37:40207 }
208
209 // Since the database was not corrupt, the entire schema and all
210 // data should be recovered.
211 ASSERT_TRUE(Reopen());
212 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
213
214 const char* kXSql = "SELECT * FROM x ORDER BY 1";
215 ASSERT_EQ("That was a test\nThis is a test",
216 ExecuteWithResults(&db(), kXSql, "|", "\n"));
217}
218
219void RecoveryCallback(sql::Connection* db, const base::FilePath& db_path,
shess6ac39542016-02-04 20:56:22220 const char* create_table, const char* create_index,
[email protected]dd325f052013-08-06 02:37:40221 int* record_error, int error, sql::Statement* stmt) {
222 *record_error = error;
223
224 // Clear the error callback to prevent reentrancy.
225 db->reset_error_callback();
226
227 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
228 ASSERT_TRUE(recovery.get());
229
shess6ac39542016-02-04 20:56:22230 ASSERT_TRUE(recovery->db()->Execute(create_table));
231 ASSERT_TRUE(recovery->db()->Execute(create_index));
[email protected]dd325f052013-08-06 02:37:40232
shess6ac39542016-02-04 20:56:22233 size_t rows = 0;
234 ASSERT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]dd325f052013-08-06 02:37:40235
dchenge48600452015-12-28 02:24:50236 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]dd325f052013-08-06 02:37:40237}
238
239// Build a database, corrupt it by making an index reference to
240// deleted row, then recover when a query selects that row.
241TEST_F(SQLRecoveryTest, RecoverCorruptIndex) {
242 const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)";
243 const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)";
244 ASSERT_TRUE(db().Execute(kCreateTable));
245 ASSERT_TRUE(db().Execute(kCreateIndex));
246
247 // Insert a bit of data.
248 {
249 ASSERT_TRUE(db().BeginTransaction());
250
251 const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (?, ?)";
252 sql::Statement s(db().GetUniqueStatement(kInsertSql));
253 for (int i = 0; i < 10; ++i) {
254 s.Reset(true);
255 s.BindInt(0, i);
256 s.BindInt(1, i);
257 EXPECT_FALSE(s.Step());
258 EXPECT_TRUE(s.Succeeded());
259 }
260
261 ASSERT_TRUE(db().CommitTransaction());
262 }
[email protected]dd325f052013-08-06 02:37:40263 db().Close();
264
[email protected]ae4f1622013-12-08 06:49:12265 // Delete a row from the table, while leaving the index entry which
266 // references it.
267 const char kDeleteSql[] = "DELETE FROM x WHERE id = 0";
268 ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x_id", kDeleteSql));
[email protected]dd325f052013-08-06 02:37:40269
270 ASSERT_TRUE(Reopen());
271
272 int error = SQLITE_OK;
shess6ac39542016-02-04 20:56:22273 db().set_error_callback(base::Bind(&RecoveryCallback, &db(), db_path(),
274 kCreateTable, kCreateIndex, &error));
[email protected]dd325f052013-08-06 02:37:40275
276 // This works before the callback is called.
277 const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master";
278 EXPECT_TRUE(db().IsSQLValid(kTrivialSql));
279
280 // TODO(shess): Could this be delete? Anything which fails should work.
281 const char kSelectSql[] = "SELECT v FROM x WHERE id = 0";
282 ASSERT_FALSE(db().Execute(kSelectSql));
283 EXPECT_EQ(SQLITE_CORRUPT, error);
284
285 // Database handle has been poisoned.
286 EXPECT_FALSE(db().IsSQLValid(kTrivialSql));
287
288 ASSERT_TRUE(Reopen());
289
290 // The recovered table should reflect the deletion.
291 const char kSelectAllSql[] = "SELECT v FROM x ORDER BY id";
292 EXPECT_EQ("1,2,3,4,5,6,7,8,9",
293 ExecuteWithResults(&db(), kSelectAllSql, "|", ","));
294
295 // The failing statement should now succeed, with no results.
296 EXPECT_EQ("", ExecuteWithResults(&db(), kSelectSql, "|", ","));
297}
298
299// Build a database, corrupt it by making a table contain a row not
300// referenced by the index, then recover the database.
301TEST_F(SQLRecoveryTest, RecoverCorruptTable) {
302 const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)";
303 const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)";
304 ASSERT_TRUE(db().Execute(kCreateTable));
305 ASSERT_TRUE(db().Execute(kCreateIndex));
306
307 // Insert a bit of data.
308 {
309 ASSERT_TRUE(db().BeginTransaction());
310
311 const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (?, ?)";
312 sql::Statement s(db().GetUniqueStatement(kInsertSql));
313 for (int i = 0; i < 10; ++i) {
314 s.Reset(true);
315 s.BindInt(0, i);
316 s.BindInt(1, i);
317 EXPECT_FALSE(s.Step());
318 EXPECT_TRUE(s.Succeeded());
319 }
320
321 ASSERT_TRUE(db().CommitTransaction());
322 }
[email protected]dd325f052013-08-06 02:37:40323 db().Close();
324
[email protected]ae4f1622013-12-08 06:49:12325 // Delete a row from the index while leaving a table entry.
326 const char kDeleteSql[] = "DELETE FROM x WHERE id = 0";
327 ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x", kDeleteSql));
[email protected]dd325f052013-08-06 02:37:40328
[email protected]dd325f052013-08-06 02:37:40329 ASSERT_TRUE(Reopen());
330
shess6ac39542016-02-04 20:56:22331 int error = SQLITE_OK;
332 db().set_error_callback(base::Bind(&RecoveryCallback, &db(), db_path(),
333 kCreateTable, kCreateIndex, &error));
334
[email protected]dd325f052013-08-06 02:37:40335 // Index shows one less than originally inserted.
336 const char kCountSql[] = "SELECT COUNT (*) FROM x";
337 EXPECT_EQ("9", ExecuteWithResults(&db(), kCountSql, "|", ","));
338
Scott Hessdcf120482015-02-10 21:33:29339 // A full table scan shows all of the original data. Using column [v] to
340 // force use of the table rather than the index.
341 const char kDistinctSql[] = "SELECT DISTINCT COUNT (v) FROM x";
[email protected]dd325f052013-08-06 02:37:40342 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ","));
343
344 // Insert id 0 again. Since it is not in the index, the insert
345 // succeeds, but results in a duplicate value in the table.
346 const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (0, 100)";
347 ASSERT_TRUE(db().Execute(kInsertSql));
348
349 // Duplication is visible.
350 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ","));
351 EXPECT_EQ("11", ExecuteWithResults(&db(), kDistinctSql, "|", ","));
352
353 // This works before the callback is called.
354 const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master";
355 EXPECT_TRUE(db().IsSQLValid(kTrivialSql));
356
shess6ac39542016-02-04 20:56:22357 // TODO(shess): Figure out a statement which causes SQLite to notice the
358 // corruption. SELECT doesn't see errors because missing index values aren't
359 // visible. UPDATE or DELETE against v=0 don't see errors, even though the
360 // index item is missing. I suspect SQLite only deletes the key in these
361 // cases, but doesn't verify that one or more keys were deleted.
362 ASSERT_FALSE(db().Execute("INSERT INTO x (id, v) VALUES (0, 101)"));
363 EXPECT_EQ(SQLITE_CONSTRAINT_UNIQUE, error);
[email protected]dd325f052013-08-06 02:37:40364
365 // Database handle has been poisoned.
366 EXPECT_FALSE(db().IsSQLValid(kTrivialSql));
367
368 ASSERT_TRUE(Reopen());
369
370 // The recovered table has consistency between the index and the table.
371 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ","));
372 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ","));
373
shess806f4992016-02-04 21:12:09374 // Only one of the values is retained.
[email protected]dd325f052013-08-06 02:37:40375 const char kSelectSql[] = "SELECT v FROM x WHERE id = 0";
shess806f4992016-02-04 21:12:09376 const std::string results = ExecuteWithResults(&db(), kSelectSql, "|", ",");
377 EXPECT_TRUE(results=="100" || results=="0") << "Actual results: " << results;
[email protected]dd325f052013-08-06 02:37:40378}
[email protected]a8848a72013-11-18 04:18:47379
380TEST_F(SQLRecoveryTest, Meta) {
381 const int kVersion = 3;
382 const int kCompatibleVersion = 2;
383
384 {
385 sql::MetaTable meta;
386 EXPECT_TRUE(meta.Init(&db(), kVersion, kCompatibleVersion));
387 EXPECT_EQ(kVersion, meta.GetVersionNumber());
388 }
389
390 // Test expected case where everything works.
391 {
392 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
393 EXPECT_TRUE(recovery->SetupMeta());
394 int version = 0;
395 EXPECT_TRUE(recovery->GetMetaVersionNumber(&version));
396 EXPECT_EQ(kVersion, version);
397
dchenge48600452015-12-28 02:24:50398 sql::Recovery::Rollback(std::move(recovery));
[email protected]a8848a72013-11-18 04:18:47399 }
400 ASSERT_TRUE(Reopen()); // Handle was poisoned.
401
402 // Test version row missing.
403 EXPECT_TRUE(db().Execute("DELETE FROM meta WHERE key = 'version'"));
404 {
405 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
406 EXPECT_TRUE(recovery->SetupMeta());
407 int version = 0;
408 EXPECT_FALSE(recovery->GetMetaVersionNumber(&version));
409 EXPECT_EQ(0, version);
410
dchenge48600452015-12-28 02:24:50411 sql::Recovery::Rollback(std::move(recovery));
[email protected]a8848a72013-11-18 04:18:47412 }
413 ASSERT_TRUE(Reopen()); // Handle was poisoned.
414
415 // Test meta table missing.
416 EXPECT_TRUE(db().Execute("DROP TABLE meta"));
417 {
418 sql::ScopedErrorIgnorer ignore_errors;
419 ignore_errors.IgnoreError(SQLITE_CORRUPT); // From virtual table.
420 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
421 EXPECT_FALSE(recovery->SetupMeta());
422 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
423 }
424}
425
426// Baseline AutoRecoverTable() test.
427TEST_F(SQLRecoveryTest, AutoRecoverTable) {
428 // BIGINT and VARCHAR to test type affinity.
429 const char kCreateSql[] = "CREATE TABLE x (id BIGINT, t TEXT, v VARCHAR)";
430 ASSERT_TRUE(db().Execute(kCreateSql));
431 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (11, 'This is', 'a test')"));
432 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, 'That was', 'a test')"));
433
434 // Save aside a copy of the original schema and data.
435 const std::string orig_schema(GetSchema(&db()));
436 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
437 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
438
439 // Create a lame-duck table which will not be propagated by recovery to
440 // detect that the recovery code actually ran.
441 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
442 ASSERT_NE(orig_schema, GetSchema(&db()));
443
444 {
445 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
446 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
447
448 // Save a copy of the temp db's schema before recovering the table.
449 const char kTempSchemaSql[] = "SELECT name, sql FROM sqlite_temp_master";
450 const std::string temp_schema(
451 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
452
453 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44454 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47455 EXPECT_EQ(2u, rows);
456
457 // Test that any additional temp tables were cleaned up.
458 EXPECT_EQ(temp_schema,
459 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
460
dchenge48600452015-12-28 02:24:50461 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47462 }
463
464 // Since the database was not corrupt, the entire schema and all
465 // data should be recovered.
466 ASSERT_TRUE(Reopen());
467 ASSERT_EQ(orig_schema, GetSchema(&db()));
468 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
469
470 // Recovery fails if the target table doesn't exist.
471 {
472 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
473 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
474
475 // TODO(shess): Should this failure implicitly lead to Raze()?
476 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44477 EXPECT_FALSE(recovery->AutoRecoverTable("y", &rows));
[email protected]a8848a72013-11-18 04:18:47478
dchenge48600452015-12-28 02:24:50479 sql::Recovery::Unrecoverable(std::move(recovery));
[email protected]a8848a72013-11-18 04:18:47480 }
481}
482
483// Test that default values correctly replace nulls. The recovery
484// virtual table reads directly from the database, so DEFAULT is not
485// interpretted at that level.
486TEST_F(SQLRecoveryTest, AutoRecoverTableWithDefault) {
487 ASSERT_TRUE(db().Execute("CREATE TABLE x (id INTEGER)"));
488 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5)"));
489 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15)"));
490
491 // ALTER effectively leaves the new columns NULL in the first two
492 // rows. The row with 17 will get the default injected at insert
493 // time, while the row with 42 will get the actual value provided.
494 // Embedded "'" to make sure default-handling continues to be quoted
495 // correctly.
496 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN t TEXT DEFAULT 'a''a'"));
497 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN b BLOB DEFAULT x'AA55'"));
498 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN i INT DEFAULT 93"));
499 ASSERT_TRUE(db().Execute("INSERT INTO x (id) VALUES (17)"));
500 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (42, 'b', x'1234', 12)"));
501
502 // Save aside a copy of the original schema and data.
503 const std::string orig_schema(GetSchema(&db()));
504 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
505 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
506
507 // Create a lame-duck table which will not be propagated by recovery to
508 // detect that the recovery code actually ran.
509 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
510 ASSERT_NE(orig_schema, GetSchema(&db()));
511
512 // Mechanically adjust the stored schema and data to allow detecting
513 // where the default value is coming from. The target table is just
514 // like the original with the default for [t] changed, to signal
515 // defaults coming from the recovery system. The two %5 rows should
516 // get the target-table default for [t], while the others should get
517 // the source-table default.
518 std::string final_schema(orig_schema);
519 std::string final_data(orig_data);
520 size_t pos;
521 while ((pos = final_schema.find("'a''a'")) != std::string::npos) {
522 final_schema.replace(pos, 6, "'c''c'");
523 }
524 while ((pos = final_data.find("5|a'a")) != std::string::npos) {
525 final_data.replace(pos, 5, "5|c'c");
526 }
527
528 {
529 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
530 // Different default to detect which table provides the default.
531 ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str()));
532
533 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44534 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47535 EXPECT_EQ(4u, rows);
536
dchenge48600452015-12-28 02:24:50537 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47538 }
539
540 // Since the database was not corrupt, the entire schema and all
541 // data should be recovered.
542 ASSERT_TRUE(Reopen());
543 ASSERT_EQ(final_schema, GetSchema(&db()));
544 ASSERT_EQ(final_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
545}
546
547// Test that rows with NULL in a NOT NULL column are filtered
548// correctly. In the wild, this would probably happen due to
549// corruption, but here it is simulated by recovering a table which
550// allowed nulls into a table which does not.
551TEST_F(SQLRecoveryTest, AutoRecoverTableNullFilter) {
552 const char kOrigSchema[] = "CREATE TABLE x (id INTEGER, t TEXT)";
553 const char kFinalSchema[] = "CREATE TABLE x (id INTEGER, t TEXT NOT NULL)";
554
555 ASSERT_TRUE(db().Execute(kOrigSchema));
556 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, null)"));
557 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15, 'this is a test')"));
558
559 // Create a lame-duck table which will not be propagated by recovery to
560 // detect that the recovery code actually ran.
561 ASSERT_EQ(kOrigSchema, GetSchema(&db()));
562 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
563 ASSERT_NE(kOrigSchema, GetSchema(&db()));
564
565 {
566 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
567 ASSERT_TRUE(recovery->db()->Execute(kFinalSchema));
568
569 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44570 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47571 EXPECT_EQ(1u, rows);
572
dchenge48600452015-12-28 02:24:50573 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47574 }
575
576 // The schema should be the same, but only one row of data should
577 // have been recovered.
578 ASSERT_TRUE(Reopen());
579 ASSERT_EQ(kFinalSchema, GetSchema(&db()));
580 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
581 ASSERT_EQ("15|this is a test", ExecuteWithResults(&db(), kXSql, "|", "\n"));
582}
583
584// Test AutoRecoverTable with a ROWID alias.
585TEST_F(SQLRecoveryTest, AutoRecoverTableWithRowid) {
586 // The rowid alias is almost always the first column, intentionally
587 // put it later.
588 const char kCreateSql[] =
589 "CREATE TABLE x (t TEXT, id INTEGER PRIMARY KEY NOT NULL)";
590 ASSERT_TRUE(db().Execute(kCreateSql));
591 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test', null)"));
592 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test', null)"));
593
594 // Save aside a copy of the original schema and data.
595 const std::string orig_schema(GetSchema(&db()));
596 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
597 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
598
599 // Create a lame-duck table which will not be propagated by recovery to
600 // detect that the recovery code actually ran.
601 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
602 ASSERT_NE(orig_schema, GetSchema(&db()));
603
604 {
605 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
606 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
607
608 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44609 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47610 EXPECT_EQ(2u, rows);
611
dchenge48600452015-12-28 02:24:50612 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47613 }
614
615 // Since the database was not corrupt, the entire schema and all
616 // data should be recovered.
617 ASSERT_TRUE(Reopen());
618 ASSERT_EQ(orig_schema, GetSchema(&db()));
619 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
620}
621
622// Test that a compound primary key doesn't fire the ROWID code.
623TEST_F(SQLRecoveryTest, AutoRecoverTableWithCompoundKey) {
624 const char kCreateSql[] =
625 "CREATE TABLE x ("
626 "id INTEGER NOT NULL,"
627 "id2 TEXT NOT NULL,"
628 "t TEXT,"
629 "PRIMARY KEY (id, id2)"
630 ")";
631 ASSERT_TRUE(db().Execute(kCreateSql));
632
633 // NOTE(shess): Do not accidentally use [id] 1, 2, 3, as those will
634 // be the ROWID values.
635 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'a', 'This is a test')"));
636 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'b', 'That was a test')"));
637 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'a', 'Another test')"));
638
639 // Save aside a copy of the original schema and data.
640 const std::string orig_schema(GetSchema(&db()));
641 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
642 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
643
644 // Create a lame-duck table which will not be propagated by recovery to
645 // detect that the recovery code actually ran.
646 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
647 ASSERT_NE(orig_schema, GetSchema(&db()));
648
649 {
650 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
651 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
652
653 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44654 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47655 EXPECT_EQ(3u, rows);
656
dchenge48600452015-12-28 02:24:50657 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47658 }
659
660 // Since the database was not corrupt, the entire schema and all
661 // data should be recovered.
662 ASSERT_TRUE(Reopen());
663 ASSERT_EQ(orig_schema, GetSchema(&db()));
664 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
665}
666
shess6f68bd32016-02-04 19:29:44667// Test recovering from a table with fewer columns than the target.
668TEST_F(SQLRecoveryTest, AutoRecoverTableMissingColumns) {
[email protected]a8848a72013-11-18 04:18:47669 const char kCreateSql[] = "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
shess6f68bd32016-02-04 19:29:44670 const char kAlterSql[] = "ALTER TABLE x ADD COLUMN t1 TEXT DEFAULT 't'";
[email protected]a8848a72013-11-18 04:18:47671 ASSERT_TRUE(db().Execute(kCreateSql));
672 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'This is')"));
673 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'That was')"));
674
shess6f68bd32016-02-04 19:29:44675 // Generate the expected info by faking a table to match what recovery will
676 // create.
[email protected]a8848a72013-11-18 04:18:47677 const std::string orig_schema(GetSchema(&db()));
678 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
shess6f68bd32016-02-04 19:29:44679 std::string expected_schema;
680 std::string expected_data;
681 {
682 ASSERT_TRUE(db().BeginTransaction());
683 ASSERT_TRUE(db().Execute(kAlterSql));
[email protected]a8848a72013-11-18 04:18:47684
shess6f68bd32016-02-04 19:29:44685 expected_schema = GetSchema(&db());
686 expected_data = ExecuteWithResults(&db(), kXSql, "|", "\n");
[email protected]a8848a72013-11-18 04:18:47687
shess6f68bd32016-02-04 19:29:44688 db().RollbackTransaction();
689 }
690
691 // Following tests are pointless if the rollback didn't work.
692 ASSERT_EQ(orig_schema, GetSchema(&db()));
693
694 // Recover the previous version of the table into the altered version.
[email protected]a8848a72013-11-18 04:18:47695 {
696 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
697 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
shess6f68bd32016-02-04 19:29:44698 ASSERT_TRUE(recovery->db()->Execute(kAlterSql));
[email protected]a8848a72013-11-18 04:18:47699 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44700 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47701 EXPECT_EQ(2u, rows);
dchenge48600452015-12-28 02:24:50702 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47703 }
704
705 // Since the database was not corrupt, the entire schema and all
706 // data should be recovered.
707 ASSERT_TRUE(Reopen());
shess6f68bd32016-02-04 19:29:44708 ASSERT_EQ(expected_schema, GetSchema(&db()));
709 ASSERT_EQ(expected_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
[email protected]a8848a72013-11-18 04:18:47710}
[email protected]cfb821612014-07-10 00:48:06711
712// Recover a golden file where an interior page has been manually modified so
713// that the number of cells is greater than will fit on a single page. This
714// case happened in <http://crbug.com/387868>.
715TEST_F(SQLRecoveryTest, Bug387868) {
716 base::FilePath golden_path;
717 ASSERT_TRUE(PathService::Get(sql::test::DIR_TEST_DATA, &golden_path));
718 golden_path = golden_path.AppendASCII("recovery_387868");
719 db().Close();
720 ASSERT_TRUE(base::CopyFile(golden_path, db_path()));
721 ASSERT_TRUE(Reopen());
722
723 {
724 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
725 ASSERT_TRUE(recovery.get());
726
727 // Create the new version of the table.
728 const char kCreateSql[] =
729 "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
730 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
731
732 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44733 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]cfb821612014-07-10 00:48:06734 EXPECT_EQ(43u, rows);
735
736 // Successfully recovered.
dchenge48600452015-12-28 02:24:50737 EXPECT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]cfb821612014-07-10 00:48:06738 }
739}
[email protected]dd325f052013-08-06 02:37:40740#endif // !defined(USE_SYSTEM_SQLITE)
741
shess2f3a814b2015-11-05 18:11:10742// Memory-mapped I/O interacts poorly with I/O errors. Make sure the recovery
743// database doesn't accidentally enable it.
744TEST_F(SQLRecoveryTest, NoMmap) {
745 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
746 ASSERT_TRUE(recovery.get());
747
748 // In the current implementation, the PRAGMA successfully runs with no result
749 // rows. Running with a single result of |0| is also acceptable.
750 sql::Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size"));
751 EXPECT_TRUE(!s.Step() || !s.ColumnInt64(0));
752}
753
[email protected]8d409412013-07-19 18:25:30754} // namespace