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.
5 #include "sql/recovery.h"
13 #include "base/feature_list.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/files/scoped_temp_dir.h"
17 #include "base/functional/bind.h"
18 #include "base/functional/callback_forward.h"
19 #include "base/functional/callback_helpers.h"
20 #include "base/path_service.h"
21 #include "base/ranges/algorithm.h"
22 #include "base/strings/strcat.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/test/bind.h"
25 #include "base/test/gtest_util.h"
26 #include "base/test/metrics/histogram_tester.h"
27 #include "base/test/scoped_feature_list.h"
28 #include "sql/database.h"
29 #include "sql/meta_table.h"
30 #include "sql/sql_features.h"
31 #include "sql/sqlite_result_code.h"
32 #include "sql/sqlite_result_code_values.h"
33 #include "sql/statement.h"
34 #include "sql/test/paths.h"
35 #include "sql/test/scoped_error_expecter.h"
36 #include "sql/test/test_helpers.h"
37 #include "testing/gtest/include/gtest/gtest.h"
38 #include "third_party/sqlite/sqlite3.h"
44 using sql::test::ExecuteWithResult;
45 using sql::test::ExecuteWithResults;
47 constexpr char kRecoveryResultHistogramName[] = "Sql.Recovery.Result";
48 constexpr char kRecoveryResultCodeHistogramName[] = "Sql.Recovery.ResultCode";
50 // Dump consistent human-readable representation of the database
51 // schema. For tables or indices, this will contain the sql command
52 // to create the table or index. For certain automatic SQLite
53 // structures with no sql, the name is used.
54 std::string GetSchema(Database* db) {
55 static const char kSql[] =
56 "SELECT COALESCE(sql, name) FROM sqlite_schema ORDER BY 1";
57 return ExecuteWithResults(db, kSql, "|", "\n");
60 // Base class for all recovery-related tests. Each subclass must initialize
61 // `scoped_feature_list_`, as appropriate.
62 class SqlRecoveryTestBase : public testing::Test {
64 void SetUp() override {
65 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
66 db_path_ = temp_dir_.GetPath().AppendASCII("recovery_test.sqlite");
67 ASSERT_TRUE(db_.Open(db_path_));
70 void TearDown() override {
74 // Ensure the database, along with any recovery files, are cleaned up.
75 ASSERT_TRUE(base::DeleteFile(db_path_));
76 ASSERT_TRUE(base::DeleteFile(db_path_.AddExtensionASCII(".backup")));
77 ASSERT_TRUE(temp_dir_.Delete());
82 return db_.Open(db_path_);
85 bool OverwriteDatabaseHeader() {
86 base::File file(db_path_,
87 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
88 static constexpr char kText[] = "Now is the winter of our discontent.";
89 constexpr int kTextBytes = sizeof(kText) - 1;
90 return file.Write(0, kText, kTextBytes) == kTextBytes;
94 base::test::ScopedFeatureList scoped_feature_list_;
95 base::ScopedTempDir temp_dir_;
96 base::FilePath db_path_;
98 base::HistogramTester histogram_tester_;
101 // Tests both the legacy `sql::Recovery` interface and the newer
102 // `sql::BuiltInRecovery` interface, if it's supported.
103 class SqlRecoveryTest : public SqlRecoveryTestBase,
104 public testing::WithParamInterface<bool> {
107 scoped_feature_list_.InitWithFeatureState(
108 features::kUseBuiltInRecoveryIfSupported, GetParam());
111 bool UseBuiltIn() { return GetParam() && BuiltInRecovery::IsSupported(); }
114 // Tests specific to the newer `sql::BuiltInRecovery` interface.
116 // Creating a new `SqlRecoveryTest` should be preferred, if possible.
117 // These tests should include a comment indicating why it is not relevant to
118 // the legacy `sql::Recovery` module.
119 class SqlBuiltInRecoveryTest : public SqlRecoveryTestBase {
121 SqlBuiltInRecoveryTest() {
122 scoped_feature_list_.InitAndEnableFeature(
123 features::kUseBuiltInRecoveryIfSupported);
127 // Tests specific to the legacy `sql::Recovery` interface.
129 // Creating a new `SqlRecoveryTest` should be preferred, if possible.
130 // These tests should include a comment indicating why it is not relevant to
131 // the new `sql::BuiltInRecovery` module.
132 class SqlLegacyRecoveryTest : public SqlRecoveryTestBase {
134 SqlLegacyRecoveryTest() {
135 scoped_feature_list_.InitAndDisableFeature(
136 features::kUseBuiltInRecoveryIfSupported);
140 TEST_F(SqlBuiltInRecoveryTest, ShouldAttemptRecovery) {
141 #if BUILDFLAG(IS_FUCHSIA)
142 // TODO(https://crbug.com/1385500): `BuiltInRecovery` is not yet supported on
144 ASSERT_FALSE(BuiltInRecovery::ShouldAttemptRecovery(&db_, SQLITE_CORRUPT));
146 // Attempt to recover from corruption.
147 ASSERT_TRUE(BuiltInRecovery::ShouldAttemptRecovery(&db_, SQLITE_CORRUPT));
149 // Do not attempt to recover from transient errors.
150 EXPECT_FALSE(BuiltInRecovery::ShouldAttemptRecovery(&db_, SQLITE_BUSY));
152 // Do not attempt to recover null databases.
153 EXPECT_FALSE(BuiltInRecovery::ShouldAttemptRecovery(nullptr, SQLITE_CORRUPT));
155 // Do not attempt to recover closed databases.
158 BuiltInRecovery::ShouldAttemptRecovery(&invalid_db, SQLITE_CORRUPT));
160 // Do not attempt to recover in-memory databases.
161 ASSERT_TRUE(invalid_db.OpenInMemory());
163 BuiltInRecovery::ShouldAttemptRecovery(&invalid_db, SQLITE_CORRUPT));
165 // Return true for databases which have an error callback set, even though
166 // the error callback should be reset before recovery is attempted.
167 db_.set_error_callback(base::DoNothing());
168 EXPECT_TRUE(BuiltInRecovery::ShouldAttemptRecovery(&db_, SQLITE_CORRUPT));
169 #endif // BUILDFLAG(IS_FUCHSIA)
172 // Baseline Recovery test covering the different ways to dispose of the scoped
173 // pointer received from Recovery::Begin().
175 // This tests behavior of the legacy corruption recovery module which is not
176 // needed in the new API. Specifically, this tests what happens to the Recovery
177 // object when it goes out of scope. The new API does not publicly expose such
179 TEST_F(SqlLegacyRecoveryTest, RecoverBasic) {
180 static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
181 static const char kInsertSql[] = "INSERT INTO x VALUES ('This is a test')";
182 static const char kAltInsertSql[] =
183 "INSERT INTO x VALUES ('That was a test')";
184 ASSERT_TRUE(db_.Execute(kCreateSql));
185 ASSERT_TRUE(db_.Execute(kInsertSql));
186 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
188 // If the Recovery handle goes out of scope without being
189 // Recovered(), the database is razed.
191 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
192 ASSERT_TRUE(recovery.get());
194 EXPECT_FALSE(db_.is_open());
195 ASSERT_TRUE(Reopen());
196 EXPECT_TRUE(db_.is_open());
197 ASSERT_EQ("", GetSchema(&db_));
199 // Recreate the database.
200 ASSERT_TRUE(db_.Execute(kCreateSql));
201 ASSERT_TRUE(db_.Execute(kInsertSql));
202 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
204 // Unrecoverable() also razes.
206 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
207 ASSERT_TRUE(recovery.get());
208 Recovery::Unrecoverable(std::move(recovery));
210 // TODO(shess): Test that calls to recover.db_ start failing.
212 EXPECT_FALSE(db_.is_open());
213 ASSERT_TRUE(Reopen());
214 EXPECT_TRUE(db_.is_open());
215 ASSERT_EQ("", GetSchema(&db_));
217 // Attempting to recover a previously-recovered handle fails early.
219 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
220 ASSERT_TRUE(recovery.get());
223 recovery = Recovery::Begin(&db_, db_path_);
224 ASSERT_FALSE(recovery.get());
226 ASSERT_TRUE(Reopen());
228 // Recreate the database.
229 ASSERT_TRUE(db_.Execute(kCreateSql));
230 ASSERT_TRUE(db_.Execute(kInsertSql));
231 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
233 // Unrecovered table to distinguish from recovered database.
234 ASSERT_TRUE(db_.Execute("CREATE TABLE y (c INTEGER)"));
235 ASSERT_NE("CREATE TABLE x (t TEXT)", GetSchema(&db_));
237 // Recovered() replaces the original with the "recovered" version.
239 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
240 ASSERT_TRUE(recovery.get());
242 // Create the new version of the table.
243 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
245 // Insert different data to distinguish from original database.
246 ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
248 // Successfully recovered.
249 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
251 EXPECT_FALSE(db_.is_open());
252 ASSERT_TRUE(Reopen());
253 EXPECT_TRUE(db_.is_open());
254 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
256 const char* kXSql = "SELECT * FROM x ORDER BY 1";
257 ASSERT_EQ("That was a test", ExecuteWithResult(&db_, kXSql));
259 // Reset the database contents.
260 ASSERT_TRUE(db_.Execute("DELETE FROM x"));
261 ASSERT_TRUE(db_.Execute(kInsertSql));
263 // Rollback() discards recovery progress and leaves the database as it was.
265 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
266 ASSERT_TRUE(recovery.get());
268 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
269 ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
271 Recovery::Rollback(std::move(recovery));
273 EXPECT_FALSE(db_.is_open());
274 ASSERT_TRUE(Reopen());
275 EXPECT_TRUE(db_.is_open());
276 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
278 ASSERT_EQ("This is a test", ExecuteWithResult(&db_, kXSql));
281 // Test operation of the virtual table used by Recovery.
283 // This tests behavior of the legacy corruption recovery module which is not
284 // needed in the new API. Specifically, this tests what happens to virtual
285 // tables added to the recovery database. The new API does not manually
286 // create the recovery database. Virtual table support is required to use
287 // the built-in module, but many of the other tests in this file would fail
288 // if virtual tables were not supported.
289 TEST_F(SqlLegacyRecoveryTest, VirtualTable) {
290 static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
291 ASSERT_TRUE(db_.Execute(kCreateSql));
292 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test')"));
293 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('That was a test')"));
295 // Successfully recover the database.
297 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
299 // Tables to recover original DB, now at [corrupt].
300 static const char kRecoveryCreateSql[] =
301 "CREATE VIRTUAL TABLE temp.recover_x using recover("
305 ASSERT_TRUE(recovery->db()->Execute(kRecoveryCreateSql));
307 // Re-create the original schema.
308 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
310 // Copy the data from the recovery tables to the new database.
311 static const char kRecoveryCopySql[] =
312 "INSERT INTO x SELECT t FROM recover_x";
313 ASSERT_TRUE(recovery->db()->Execute(kRecoveryCopySql));
315 // Successfully recovered.
316 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
319 // Since the database was not corrupt, the entire schema and all
320 // data should be recovered.
321 ASSERT_TRUE(Reopen());
322 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
324 static const char* kXSql = "SELECT * FROM x ORDER BY 1";
325 ASSERT_EQ("That was a test\nThis is a test",
326 ExecuteWithResults(&db_, kXSql, "|", "\n"));
329 // Our corruption handling assumes that a corrupt index doesn't impact
330 // SQL statements that only operate on the associated table. This test verifies
333 // This tests an assumption of the legacy corruption recovery module which is
334 // irrelevant to the new API. Specifically, that a corrupt index doesn't
335 // impact SQL statements that only operate on the associated table.
336 TEST_F(SqlLegacyRecoveryTest, TableIndependentFromCorruptIndex) {
337 static const char kCreateTable[] =
338 "CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL)";
339 ASSERT_TRUE(db_.Execute(kCreateTable));
340 ASSERT_TRUE(db_.Execute("CREATE UNIQUE INDEX rows_index ON rows(indexed)"));
342 // Populate the table with powers of two. These numbers make it easy to see if
343 // SUM() missed a row.
344 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(1, 1)"));
345 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(2, 2)"));
346 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(4, 4)"));
347 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(8, 8)"));
349 // SQL statement that performs a table scan. SUM(unindexed) heavily nudges
350 // SQLite to use the table instead of the index.
351 static const char kUnindexedCountSql[] = "SELECT SUM(unindexed) FROM rows";
352 EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
353 << "No SQL statement should fail before corruption";
355 // SQL statement that performs an index scan.
356 static const char kIndexedCountSql[] =
357 "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
358 EXPECT_EQ("15", ExecuteWithResult(&db_, kIndexedCountSql))
359 << "Table scan should not fail due to corrupt index";
362 ASSERT_TRUE(sql::test::CorruptIndexRootPage(db_path_, "rows_index"));
363 ASSERT_TRUE(Reopen());
366 sql::test::ScopedErrorExpecter expecter;
367 expecter.ExpectError(SQLITE_CORRUPT);
368 EXPECT_FALSE(db_.Execute(kIndexedCountSql))
369 << "Index scan on corrupt index should fail";
370 EXPECT_TRUE(expecter.SawExpectedErrors())
371 << "Index scan on corrupt index should fail";
374 EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
375 << "Table scan should not fail due to corrupt index";
378 TEST_P(SqlRecoveryTest, RecoverCorruptIndex) {
379 static const char kCreateTable[] =
380 "CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL)";
381 ASSERT_TRUE(db_.Execute(kCreateTable));
383 static const char kCreateIndex[] =
384 "CREATE UNIQUE INDEX rows_index ON rows(indexed)";
385 ASSERT_TRUE(db_.Execute(kCreateIndex));
387 // Populate the table with powers of two. These numbers make it easy to see if
388 // SUM() missed a row.
389 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(1, 1)"));
390 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(2, 2)"));
391 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(4, 4)"));
392 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(8, 8)"));
395 ASSERT_TRUE(sql::test::CorruptIndexRootPage(db_path_, "rows_index"));
396 ASSERT_TRUE(Reopen());
398 int error = SQLITE_OK;
399 db_.set_error_callback(
400 base::BindLambdaForTesting([&](int sqlite_error, Statement* statement) {
401 error = sqlite_error;
403 // Recovery::Begin() does not support a pre-existing error callback.
404 db_.reset_error_callback();
407 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
408 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
409 SqliteResultCode::kOk);
410 histogram_tester_.ExpectUniqueSample(
411 kRecoveryResultHistogramName, BuiltInRecovery::Result::kSuccess,
412 /*expected_bucket_count=*/1);
413 histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
414 SqliteLoggedResultCode::kNoError,
415 /*expected_bucket_count=*/1);
419 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
420 ASSERT_TRUE(recovery.get());
422 ASSERT_TRUE(recovery->db()->Execute(kCreateTable));
423 ASSERT_TRUE(recovery->db()->Execute(kCreateIndex));
426 ASSERT_TRUE(recovery->AutoRecoverTable("rows", &rows));
427 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
430 // SUM(unindexed) heavily nudges SQLite to use the table instead of the index.
431 static const char kUnindexedCountSql[] = "SELECT SUM(unindexed) FROM rows";
432 EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
433 << "Table scan should not fail due to corrupt index";
434 EXPECT_EQ(SQLITE_OK, error)
435 << "Successful statement execution should not invoke the error callback";
437 static const char kIndexedCountSql[] =
438 "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
439 EXPECT_EQ("", ExecuteWithResult(&db_, kIndexedCountSql))
440 << "Index scan on corrupt index should fail";
441 EXPECT_EQ(SQLITE_CORRUPT, error)
442 << "Error callback should be called during scan on corrupt index";
444 EXPECT_EQ("", ExecuteWithResult(&db_, kUnindexedCountSql))
445 << "Table scan should not succeed anymore on a poisoned database";
447 ASSERT_TRUE(Reopen());
449 // The recovered table has consistency between the index and the table.
450 EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
451 << "Table should survive database recovery";
452 EXPECT_EQ("15", ExecuteWithResult(&db_, kIndexedCountSql))
453 << "Index should be reconstructed during database recovery";
456 TEST_P(SqlRecoveryTest, RecoverCorruptTable) {
457 // The `filler` column is used to cause a record to overflow multiple pages.
458 static const char kCreateTable[] =
460 "CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL,"
461 "filler BLOB NOT NULL)";
463 ASSERT_TRUE(db_.Execute(kCreateTable));
465 static const char kCreateIndex[] =
466 "CREATE UNIQUE INDEX rows_index ON rows(indexed)";
467 ASSERT_TRUE(db_.Execute(kCreateIndex));
469 // Populate the table with powers of two. These numbers make it easy to see if
470 // SUM() missed a row.
471 ASSERT_TRUE(db_.Execute(
472 "INSERT INTO rows(indexed, unindexed, filler) VALUES(1, 1, x'31')"));
473 ASSERT_TRUE(db_.Execute(
474 "INSERT INTO rows(indexed, unindexed, filler) VALUES(2, 2, x'32')"));
475 ASSERT_TRUE(db_.Execute(
476 "INSERT INTO rows(indexed, unindexed, filler) VALUES(4, 4, x'34')"));
478 constexpr int kDbPageSize = 4096;
480 // Insert a record that will overflow the page.
481 std::vector<uint8_t> large_buffer;
482 ASSERT_EQ(db_.page_size(), kDbPageSize)
483 << "Page overflow relies on specific size";
484 large_buffer.resize(kDbPageSize * 2);
485 base::ranges::fill(large_buffer, '8');
486 sql::Statement insert(db_.GetUniqueStatement(
487 "INSERT INTO rows(indexed,unindexed,filler) VALUES(8,8,?)"));
488 insert.BindBlob(0, large_buffer);
489 ASSERT_TRUE(insert.Run());
494 // Zero out the last page of the database. This should be the overflow page
495 // allocated for the last inserted row. So, deleting it should corrupt the
497 base::File db_file(db_path_, base::File::FLAG_OPEN | base::File::FLAG_READ |
498 base::File::FLAG_WRITE);
499 ASSERT_TRUE(db_file.IsValid());
500 int64_t db_size = db_file.GetLength();
501 ASSERT_GT(db_size, kDbPageSize)
502 << "The database should have multiple pages";
503 ASSERT_TRUE(db_file.SetLength(db_size - kDbPageSize));
507 sql::test::ScopedErrorExpecter expecter;
508 expecter.ExpectError(SQLITE_CORRUPT);
509 ASSERT_TRUE(Reopen());
510 EXPECT_TRUE(expecter.SawExpectedErrors());
511 // PRAGMAs executed inside Database::Open() will error out.
514 int error = SQLITE_OK;
515 db_.set_error_callback(
516 base::BindLambdaForTesting([&](int sqlite_error, Statement* statement) {
517 error = sqlite_error;
519 // Recovery::Begin() does not support a pre-existing error callback.
520 db_.reset_error_callback();
523 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
524 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
525 SqliteResultCode::kOk);
529 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
530 ASSERT_TRUE(recovery.get());
532 ASSERT_TRUE(recovery->db()->Execute(kCreateTable));
533 ASSERT_TRUE(recovery->db()->Execute(kCreateIndex));
536 ASSERT_TRUE(recovery->AutoRecoverTable("rows", &rows));
537 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
540 // SUM(unindexed) heavily nudges SQLite to use the table instead of the index.
541 static const char kUnindexedCountSql[] = "SELECT SUM(unindexed) FROM rows";
542 EXPECT_FALSE(db_.Execute(kUnindexedCountSql))
543 << "Table scan on corrupt table should fail";
544 EXPECT_EQ(SQLITE_CORRUPT, error)
545 << "Error callback should be called during scan on corrupt index";
547 ASSERT_TRUE(Reopen());
549 // All rows should be recovered. Only the BLOB in the last row was damaged.
550 EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
551 << "Table should survive database recovery";
552 static const char kIndexedCountSql[] =
553 "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
554 EXPECT_EQ("15", ExecuteWithResult(&db_, kIndexedCountSql))
555 << "Index should be reconstructed during database recovery";
558 TEST_P(SqlRecoveryTest, Meta) {
559 const int kVersion = 3;
560 const int kCompatibleVersion = 2;
564 EXPECT_TRUE(meta.Init(&db_, kVersion, kCompatibleVersion));
565 EXPECT_EQ(kVersion, meta.GetVersionNumber());
568 // Test expected case where everything works.
571 BuiltInRecovery::RecoverDatabase(
572 &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
573 SqliteResultCode::kOk);
574 histogram_tester_.ExpectUniqueSample(kRecoveryResultHistogramName,
575 BuiltInRecovery::Result::kSuccess,
576 /*expected_bucket_count=*/1);
577 histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
578 SqliteLoggedResultCode::kNoError,
579 /*expected_bucket_count=*/1);
581 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
582 EXPECT_TRUE(recovery->SetupMeta());
584 EXPECT_TRUE(recovery->GetMetaVersionNumber(&version));
585 EXPECT_EQ(kVersion, version);
587 Recovery::Rollback(std::move(recovery));
590 ASSERT_TRUE(Reopen()); // Handle was poisoned.
592 ASSERT_TRUE(db_.DoesTableExist("meta"));
594 // Test version row missing.
595 EXPECT_TRUE(db_.Execute("DELETE FROM meta WHERE key = 'version'"));
599 BuiltInRecovery::RecoverDatabase(
600 &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
601 SqliteResultCode::kError);
602 histogram_tester_.ExpectBucketCount(
603 kRecoveryResultHistogramName,
604 BuiltInRecovery::Result::kFailedMetaTableVersionWasInvalid,
605 /*expected_count=*/1);
606 histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
607 SqliteLoggedResultCode::kNoError,
608 /*expected_bucket_count=*/2);
610 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
611 EXPECT_TRUE(recovery->SetupMeta());
613 EXPECT_FALSE(recovery->GetMetaVersionNumber(&version));
614 EXPECT_EQ(0, version);
616 Recovery::Rollback(std::move(recovery));
618 ASSERT_TRUE(Reopen()); // Handle was poisoned.
620 // Test meta table missing.
622 ASSERT_FALSE(db_.DoesTableExist("meta"));
625 BuiltInRecovery::RecoverDatabase(
626 &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
627 SqliteResultCode::kError);
628 histogram_tester_.ExpectBucketCount(
629 kRecoveryResultHistogramName,
630 BuiltInRecovery::Result::kFailedMetaTableDoesNotExist,
631 /*expected_count=*/1);
632 histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
633 SqliteLoggedResultCode::kNoError,
634 /*expected_bucket_count=*/3);
636 // The table was rolled back after the recovery failure. Manually drop the
638 ASSERT_TRUE(db_.DoesTableExist("meta"));
639 EXPECT_TRUE(db_.Execute("DROP TABLE meta"));
641 sql::test::ScopedErrorExpecter expecter;
642 expecter.ExpectError(SQLITE_CORRUPT); // From virtual table.
643 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
644 EXPECT_FALSE(recovery->SetupMeta());
645 ASSERT_TRUE(expecter.SawExpectedErrors());
649 // Baseline AutoRecoverTable() test.
650 TEST_P(SqlRecoveryTest, AutoRecoverTable) {
651 // BIGINT and VARCHAR to test type affinity.
652 static const char kCreateSql[] =
653 "CREATE TABLE x (id BIGINT, t TEXT, v VARCHAR)";
654 ASSERT_TRUE(db_.Execute(kCreateSql));
655 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (11, 'This is', 'a test')"));
656 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (5, 'That was', 'a test')"));
658 // Save aside a copy of the original schema and data.
659 const std::string orig_schema(GetSchema(&db_));
660 static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
661 const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
664 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
665 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
666 SqliteResultCode::kOk);
668 // Create a lame-duck table which will not be propagated by recovery to
669 // detect that the recovery code actually ran.
670 ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
671 ASSERT_NE(orig_schema, GetSchema(&db_));
673 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
674 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
676 // Save a copy of the temp db's schema before recovering the table.
677 static const char kTempSchemaSql[] =
678 "SELECT name, sql FROM sqlite_temp_schema";
679 const std::string temp_schema(
680 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
683 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
686 // Test that any additional temp tables were cleaned up.
687 EXPECT_EQ(temp_schema,
688 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
690 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
693 // Since the database was not corrupt, the entire schema and all
694 // data should be recovered.
695 ASSERT_TRUE(Reopen());
696 ASSERT_EQ(orig_schema, GetSchema(&db_));
697 ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
699 // Recovery fails if the target table doesn't exist.
701 // ... or it can succeed silently, since there's nothing to do.
702 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
703 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
704 SqliteResultCode::kOk);
706 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
707 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
709 // TODO(shess): Should this failure implicitly lead to Raze()?
711 EXPECT_FALSE(recovery->AutoRecoverTable("y", &rows));
713 Recovery::Unrecoverable(std::move(recovery));
717 // Test that default values correctly replace nulls. The recovery
718 // virtual table reads directly from the database, so DEFAULT is not
719 // interpreted at that level.
720 TEST_P(SqlRecoveryTest, AutoRecoverTableWithDefault) {
721 ASSERT_TRUE(db_.Execute("CREATE TABLE x (id INTEGER)"));
722 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (5)"));
723 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (15)"));
725 // ALTER effectively leaves the new columns NULL in the first two
726 // rows. The row with 17 will get the default injected at insert
727 // time, while the row with 42 will get the actual value provided.
728 // Embedded "'" to make sure default-handling continues to be quoted
730 ASSERT_TRUE(db_.Execute("ALTER TABLE x ADD COLUMN t TEXT DEFAULT 'a''a'"));
731 ASSERT_TRUE(db_.Execute("ALTER TABLE x ADD COLUMN b BLOB DEFAULT x'AA55'"));
732 ASSERT_TRUE(db_.Execute("ALTER TABLE x ADD COLUMN i INT DEFAULT 93"));
733 ASSERT_TRUE(db_.Execute("INSERT INTO x (id) VALUES (17)"));
734 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (42, 'b', x'1234', 12)"));
736 // Save aside a copy of the original schema and data.
737 const std::string orig_schema(GetSchema(&db_));
738 static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
739 const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
741 std::string final_schema(orig_schema);
742 std::string final_data(orig_data);
744 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
745 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
746 SqliteResultCode::kOk);
748 // Create a lame-duck table which will not be propagated by recovery to
749 // detect that the recovery code actually ran.
750 ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
751 ASSERT_NE(orig_schema, GetSchema(&db_));
753 // Mechanically adjust the stored schema and data to allow detecting
754 // where the default value is coming from. The target table is just
755 // like the original with the default for [t] changed, to signal
756 // defaults coming from the recovery system. The two %5 rows should
757 // get the target-table default for [t], while the others should get
758 // the source-table default.
760 while ((pos = final_schema.find("'a''a'")) != std::string::npos) {
761 final_schema.replace(pos, 6, "'c''c'");
763 while ((pos = final_data.find("5|a'a")) != std::string::npos) {
764 final_data.replace(pos, 5, "5|c'c");
766 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
767 // Different default to detect which table provides the default.
768 ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str()));
771 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
774 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
777 // Since the database was not corrupt, the entire schema and all
778 // data should be recovered.
779 ASSERT_TRUE(Reopen());
780 ASSERT_EQ(final_schema, GetSchema(&db_));
781 ASSERT_EQ(final_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
784 // Test that rows with NULL in a NOT NULL column are filtered
785 // correctly. In the wild, this would probably happen due to
786 // corruption, but here it is simulated by recovering a table which
787 // allowed nulls into a table which does not.
789 // This tests behavior of the legacy corruption recovery module which is not
790 // needed in the new API. Specifically, this tests what happens to tables
791 // with NULL values in non-nullable columns. This test is not easily
792 // replicated, since SQLite does not support ALTER COLUMN. We'll trust that
793 // it's handled properly upstream.
794 TEST_F(SqlLegacyRecoveryTest, AutoRecoverTableNullFilter) {
795 static const char kOrigSchema[] = "CREATE TABLE x (id INTEGER, t TEXT)";
796 static const char kFinalSchema[] =
797 "CREATE TABLE x (id INTEGER, t TEXT NOT NULL)";
799 ASSERT_TRUE(db_.Execute(kOrigSchema));
800 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (5, NULL)"));
801 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (15, 'this is a test')"));
803 // Create a lame-duck table which will not be propagated by recovery to
804 // detect that the recovery code actually ran.
805 ASSERT_EQ(kOrigSchema, GetSchema(&db_));
806 ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
807 ASSERT_NE(kOrigSchema, GetSchema(&db_));
810 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
811 ASSERT_TRUE(recovery->db()->Execute(kFinalSchema));
814 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
817 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
820 // The schema should be the same, but only one row of data should
821 // have been recovered.
822 ASSERT_TRUE(Reopen());
823 ASSERT_EQ(kFinalSchema, GetSchema(&db_));
824 static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
825 ASSERT_EQ("15|this is a test", ExecuteWithResults(&db_, kXSql, "|", "\n"));
828 // Test AutoRecoverTable with a ROWID alias.
829 TEST_P(SqlRecoveryTest, AutoRecoverTableWithRowid) {
830 // The rowid alias is almost always the first column, intentionally
832 static const char kCreateSql[] =
833 "CREATE TABLE x (t TEXT, id INTEGER PRIMARY KEY NOT NULL)";
834 ASSERT_TRUE(db_.Execute(kCreateSql));
835 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test', NULL)"));
836 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('That was a test', NULL)"));
838 // Save aside a copy of the original schema and data.
839 const std::string orig_schema(GetSchema(&db_));
840 static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
841 const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
844 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
845 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
846 SqliteResultCode::kOk);
848 // Create a lame-duck table which will not be propagated by recovery to
849 // detect that the recovery code actually ran.
850 ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
851 ASSERT_NE(orig_schema, GetSchema(&db_));
853 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
854 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
857 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
860 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
863 // Since the database was not corrupt, the entire schema and all
864 // data should be recovered.
865 ASSERT_TRUE(Reopen());
866 ASSERT_EQ(orig_schema, GetSchema(&db_));
867 ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
870 // Test that a compound primary key doesn't fire the ROWID code.
872 // This tests behavior of the legacy corruption recovery module which is not
873 // needed in the new API. Specifically, this tests how ROWID tables are
875 TEST_F(SqlLegacyRecoveryTest, AutoRecoverTableWithCompoundKey) {
876 static const char kCreateSql[] =
878 "id INTEGER NOT NULL,"
881 "PRIMARY KEY (id, id2)"
883 ASSERT_TRUE(db_.Execute(kCreateSql));
885 // NOTE(shess): Do not accidentally use [id] 1, 2, 3, as those will
886 // be the ROWID values.
887 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'a', 'This is a test')"));
888 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'b', 'That was a test')"));
889 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (2, 'a', 'Another test')"));
891 // Save aside a copy of the original schema and data.
892 const std::string orig_schema(GetSchema(&db_));
893 static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
894 const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
896 // Create a lame-duck table which will not be propagated by recovery to
897 // detect that the recovery code actually ran.
898 ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
899 ASSERT_NE(orig_schema, GetSchema(&db_));
902 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
903 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
906 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
909 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
912 // Since the database was not corrupt, the entire schema and all
913 // data should be recovered.
914 ASSERT_TRUE(Reopen());
915 ASSERT_EQ(orig_schema, GetSchema(&db_));
916 ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
919 // Test recovering from a table with fewer columns than the target.
921 // This tests behavior of the legacy corruption recovery module which is not
922 // needed in the new API. Specifically, this tests how tables with missing
923 // columns are handled.
924 TEST_F(SqlLegacyRecoveryTest, AutoRecoverTableMissingColumns) {
925 static const char kCreateSql[] =
926 "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
927 static const char kAlterSql[] =
928 "ALTER TABLE x ADD COLUMN t1 TEXT DEFAULT 't'";
929 ASSERT_TRUE(db_.Execute(kCreateSql));
930 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'This is')"));
931 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (2, 'That was')"));
933 // Generate the expected info by faking a table to match what recovery will
935 const std::string orig_schema(GetSchema(&db_));
936 static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
937 std::string expected_schema;
938 std::string expected_data;
940 ASSERT_TRUE(db_.BeginTransaction());
941 ASSERT_TRUE(db_.Execute(kAlterSql));
943 expected_schema = GetSchema(&db_);
944 expected_data = ExecuteWithResults(&db_, kXSql, "|", "\n");
946 db_.RollbackTransaction();
949 // Following tests are pointless if the rollback didn't work.
950 ASSERT_EQ(orig_schema, GetSchema(&db_));
952 // Recover the previous version of the table into the altered version.
954 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
955 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
956 ASSERT_TRUE(recovery->db()->Execute(kAlterSql));
958 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
960 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
963 // Since the database was not corrupt, the entire schema and all
964 // data should be recovered.
965 ASSERT_TRUE(Reopen());
966 ASSERT_EQ(expected_schema, GetSchema(&db_));
967 ASSERT_EQ(expected_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
970 // Recover a golden file where an interior page has been manually modified so
971 // that the number of cells is greater than will fit on a single page. This
972 // case happened in <http://crbug.com/387868>.
973 TEST_P(SqlRecoveryTest, Bug387868) {
974 base::FilePath golden_path;
975 ASSERT_TRUE(base::PathService::Get(sql::test::DIR_TEST_DATA, &golden_path));
976 golden_path = golden_path.AppendASCII("recovery_387868");
978 ASSERT_TRUE(base::CopyFile(golden_path, db_path_));
979 ASSERT_TRUE(Reopen());
982 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
983 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
984 SqliteResultCode::kOk);
986 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
987 ASSERT_TRUE(recovery.get());
989 // Create the new version of the table.
990 static const char kCreateSql[] =
991 "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
992 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
995 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
996 EXPECT_EQ(43u, rows);
998 // Successfully recovered.
999 EXPECT_TRUE(Recovery::Recovered(std::move(recovery)));
1003 // Memory-mapped I/O interacts poorly with I/O errors. Make sure the recovery
1004 // database doesn't accidentally enable it.
1006 // This tests behavior of the legacy corruption recovery module which is not
1007 // needed in the new API. Specifically, that MMAPing is disabled.
1008 TEST_F(SqlLegacyRecoveryTest, NoMmap) {
1009 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
1010 ASSERT_TRUE(recovery.get());
1012 // In the current implementation, the PRAGMA successfully runs with no result
1013 // rows. Running with a single result of |0| is also acceptable.
1014 Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size"));
1015 EXPECT_TRUE(!s.Step() || !s.ColumnInt64(0));
1018 void TestRecoverDatabase(Database& db,
1019 const base::FilePath& db_path,
1021 base::OnceClosure run_recovery) {
1022 const int kVersion = 3;
1023 const int kCompatibleVersion = 2;
1027 EXPECT_TRUE(meta.Init(&db, kVersion, kCompatibleVersion));
1028 EXPECT_EQ(kVersion, meta.GetVersionNumber());
1029 EXPECT_EQ(kCompatibleVersion, meta.GetCompatibleVersionNumber());
1032 // As a side effect, AUTOINCREMENT creates the sqlite_sequence table for
1033 // RecoverDatabase() to handle.
1034 ASSERT_TRUE(db.Execute(
1035 "CREATE TABLE table1(id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT)"));
1036 EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('turtle')"));
1037 EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('truck')"));
1038 EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('trailer')"));
1040 // This table needs index and a unique index to work.
1041 ASSERT_TRUE(db.Execute("CREATE TABLE table2(name TEXT, value TEXT)"));
1042 ASSERT_TRUE(db.Execute("CREATE UNIQUE INDEX table2_name ON table2(name)"));
1043 ASSERT_TRUE(db.Execute("CREATE INDEX table2_value ON table2(value)"));
1045 db.Execute("INSERT INTO table2(name, value) VALUES('jim', 'telephone')"));
1047 db.Execute("INSERT INTO table2(name, value) VALUES('bob', 'truck')"));
1049 db.Execute("INSERT INTO table2(name, value) VALUES('dean', 'trailer')"));
1051 // Save aside a copy of the original schema, verifying that it has the created
1052 // items plus the sqlite_sequence table.
1053 const std::string original_schema = GetSchema(&db);
1054 ASSERT_EQ(with_meta ? 6 : 4, base::ranges::count(original_schema, '\n'))
1057 static constexpr char kTable1Sql[] = "SELECT * FROM table1 ORDER BY 1";
1058 static constexpr char kTable2Sql[] = "SELECT * FROM table2 ORDER BY 1";
1059 EXPECT_EQ("1|turtle\n2|truck\n3|trailer",
1060 ExecuteWithResults(&db, kTable1Sql, "|", "\n"));
1061 EXPECT_EQ("bob|truck\ndean|trailer\njim|telephone",
1062 ExecuteWithResults(&db, kTable2Sql, "|", "\n"));
1064 // Database handle is valid before recovery, poisoned after.
1065 static constexpr char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_schema";
1066 EXPECT_TRUE(db.IsSQLValid(kTrivialSql));
1068 std::move(run_recovery).Run();
1070 EXPECT_FALSE(db.is_open());
1072 // Since the database was not corrupt, the entire schema and all data should
1073 // be recovered. Re-open the database.
1075 ASSERT_TRUE(db.Open(db_path));
1076 ASSERT_EQ(original_schema, GetSchema(&db));
1077 EXPECT_EQ("1|turtle\n2|truck\n3|trailer",
1078 ExecuteWithResults(&db, kTable1Sql, "|", "\n"));
1079 EXPECT_EQ("bob|truck\ndean|trailer\njim|telephone",
1080 ExecuteWithResults(&db, kTable2Sql, "|", "\n"));
1084 EXPECT_TRUE(meta.Init(&db, kVersion, kCompatibleVersion));
1085 EXPECT_EQ(kVersion, meta.GetVersionNumber());
1086 EXPECT_EQ(kCompatibleVersion, meta.GetCompatibleVersionNumber());
1090 TEST_P(SqlRecoveryTest, RecoverDatabase) {
1091 auto run_recovery = base::BindLambdaForTesting([&]() {
1093 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1094 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
1095 SqliteResultCode::kOk);
1097 Recovery::RecoverDatabase(&db_, db_path_);
1101 TestRecoverDatabase(db_, db_path_, /*with_meta=*/false,
1102 std::move(run_recovery));
1105 TEST_P(SqlRecoveryTest, RecoverDatabaseMeta) {
1106 auto run_recovery = base::BindLambdaForTesting([&]() {
1109 BuiltInRecovery::RecoverDatabase(
1110 &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
1111 SqliteResultCode::kOk);
1113 Recovery::RecoverDatabaseWithMetaVersion(&db_, db_path_);
1117 TestRecoverDatabase(db_, db_path_, /*with_meta=*/true,
1118 std::move(run_recovery));
1121 TEST_P(SqlRecoveryTest, RecoverIfPossible) {
1122 auto run_recovery = base::BindLambdaForTesting([&]() {
1123 EXPECT_TRUE(BuiltInRecovery::RecoverIfPossible(
1124 &db_, SQLITE_CORRUPT, BuiltInRecovery::Strategy::kRecoverOrRaze));
1127 TestRecoverDatabase(db_, db_path_, /*with_meta=*/false,
1128 std::move(run_recovery));
1131 TEST_P(SqlRecoveryTest, RecoverIfPossibleMeta) {
1132 auto run_recovery = base::BindLambdaForTesting([&]() {
1133 EXPECT_TRUE(BuiltInRecovery::RecoverIfPossible(
1134 &db_, SQLITE_CORRUPT,
1135 BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze));
1138 TestRecoverDatabase(db_, db_path_, /*with_meta=*/true,
1139 std::move(run_recovery));
1142 TEST_P(SqlRecoveryTest, RecoverIfPossibleWithErrorCallback) {
1143 auto run_recovery = base::BindLambdaForTesting([&]() {
1144 db_.set_error_callback(base::DoNothing());
1145 // The error callback should be reset during `RecoverIfPossible()` if
1146 // recovery was attempted.
1147 bool recovery_was_attempted = BuiltInRecovery::RecoverIfPossible(
1148 &db_, SQLITE_CORRUPT,
1149 BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze);
1150 EXPECT_TRUE(recovery_was_attempted);
1151 EXPECT_NE(db_.has_error_callback(), recovery_was_attempted);
1154 TestRecoverDatabase(db_, db_path_, /*with_meta=*/true,
1155 std::move(run_recovery));
1158 TEST_P(SqlRecoveryTest, RecoverIfPossibleWithClosedDatabase) {
1159 auto run_recovery = base::BindLambdaForTesting([&]() {
1160 // Recovery should not be attempted on a closed database.
1163 EXPECT_FALSE(BuiltInRecovery::RecoverIfPossible(
1164 &db_, SQLITE_CORRUPT, BuiltInRecovery::Strategy::kRecoverOrRaze));
1167 TestRecoverDatabase(db_, db_path_, /*with_meta=*/false,
1168 std::move(run_recovery));
1171 TEST_P(SqlRecoveryTest, RecoverIfPossibleWithPerDatabaseUma) {
1172 auto run_recovery = base::BindLambdaForTesting([&]() {
1173 EXPECT_TRUE(BuiltInRecovery::RecoverIfPossible(
1174 &db_, SQLITE_CORRUPT, BuiltInRecovery::Strategy::kRecoverOrRaze,
1175 &features::kUseBuiltInRecoveryIfSupported, "MyFeatureDatabase"));
1178 TestRecoverDatabase(db_, db_path_, /*with_meta=*/false,
1179 std::move(run_recovery));
1182 // Log to the overall histograms.
1183 histogram_tester_.ExpectUniqueSample(kRecoveryResultHistogramName,
1184 BuiltInRecovery::Result::kSuccess,
1185 /*expected_bucket_count=*/1);
1186 histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
1187 SqliteLoggedResultCode::kNoError,
1188 /*expected_bucket_count=*/1);
1189 // And the histograms for this specific feature.
1190 histogram_tester_.ExpectUniqueSample(
1191 base::StrCat({kRecoveryResultHistogramName, ".MyFeatureDatabase"}),
1192 BuiltInRecovery::Result::kSuccess,
1193 /*expected_bucket_count=*/1);
1194 histogram_tester_.ExpectUniqueSample(
1195 base::StrCat({kRecoveryResultCodeHistogramName, ".MyFeatureDatabase"}),
1196 SqliteLoggedResultCode::kNoError,
1197 /*expected_bucket_count=*/1);
1201 TEST_P(SqlRecoveryTest, RecoverDatabaseWithView) {
1203 sql::Database db({.enable_views_discouraged = true});
1204 ASSERT_TRUE(db.Open(db_path_));
1206 ASSERT_TRUE(db.Execute(
1207 "CREATE TABLE table1(id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT)"));
1208 EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('turtle')"));
1209 EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('truck')"));
1210 EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('trailer')"));
1212 ASSERT_TRUE(db.Execute("CREATE TABLE table2(name TEXT, value TEXT)"));
1213 ASSERT_TRUE(db.Execute("CREATE UNIQUE INDEX table2_name ON table2(name)"));
1215 db.Execute("INSERT INTO table2(name, value) VALUES('jim', 'telephone')"));
1217 db.Execute("INSERT INTO table2(name, value) VALUES('bob', 'truck')"));
1219 db.Execute("INSERT INTO table2(name, value) VALUES('dean', 'trailer')"));
1221 // View which is the intersection of [table1.value] and [table2.value].
1222 ASSERT_TRUE(db.Execute(
1223 "CREATE VIEW view_table12 AS SELECT table1.value FROM table1, table2 "
1224 "WHERE table1.value = table2.value"));
1226 static constexpr char kViewSql[] = "SELECT * FROM view_table12 ORDER BY 1";
1227 EXPECT_EQ("trailer\ntruck", ExecuteWithResults(&db, kViewSql, "|", "\n"));
1229 // Save aside a copy of the original schema, verifying that it has the created
1230 // items plus the sqlite_sequence table.
1231 const std::string original_schema = GetSchema(&db);
1232 ASSERT_EQ(4, base::ranges::count(original_schema, '\n')) << original_schema;
1234 // Database handle is valid before recovery, poisoned after.
1235 static constexpr char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_schema";
1236 EXPECT_TRUE(db.IsSQLValid(kTrivialSql));
1238 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1239 &db, BuiltInRecovery::Strategy::kRecoverOrRaze),
1240 SqliteResultCode::kOk);
1242 Recovery::RecoverDatabase(&db, db_path_);
1244 EXPECT_FALSE(db.IsSQLValid(kTrivialSql));
1246 // Since the database was not corrupt, the entire schema and all data should
1249 ASSERT_TRUE(db.Open(db_path_));
1250 EXPECT_EQ("trailer\ntruck", ExecuteWithResults(&db, kViewSql, "|", "\n"));
1253 // When RecoverDatabase() encounters SQLITE_NOTADB, the database is deleted.
1254 TEST_P(SqlRecoveryTest, RecoverDatabaseDelete) {
1255 // Create a valid database, then write junk over the header. This should lead
1256 // to SQLITE_NOTADB, which will cause ATTACH to fail.
1257 ASSERT_TRUE(db_.Execute("CREATE TABLE x (t TEXT)"));
1258 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test')"));
1260 ASSERT_TRUE(OverwriteDatabaseHeader());
1263 sql::test::ScopedErrorExpecter expecter;
1264 expecter.ExpectError(SQLITE_NOTADB);
1266 // Reopen() here because it will see SQLITE_NOTADB.
1267 ASSERT_TRUE(Reopen());
1269 // This should "recover" the database by making it valid, but empty.
1271 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1272 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
1273 SqliteResultCode::kNotADatabase);
1274 histogram_tester_.ExpectUniqueSample(
1275 kRecoveryResultHistogramName,
1276 BuiltInRecovery::Result::kFailedRecoveryRun,
1277 /*expected_bucket_count=*/1);
1278 histogram_tester_.ExpectUniqueSample(
1279 kRecoveryResultCodeHistogramName,
1280 SqliteLoggedResultCode::kNotADatabase,
1281 /*expected_bucket_count=*/1);
1283 Recovery::RecoverDatabase(&db_, db_path_);
1285 ASSERT_TRUE(expecter.SawExpectedErrors());
1288 // Recovery poisoned the handle, must re-open.
1290 ASSERT_TRUE(Reopen());
1292 EXPECT_EQ("", GetSchema(&db_));
1295 // Allow callers to validate the database between recovery and commit.
1296 TEST_P(SqlRecoveryTest, BeginRecoverDatabase) {
1297 static const char kCreateTable[] =
1298 "CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL)";
1299 ASSERT_TRUE(db_.Execute(kCreateTable));
1301 ASSERT_TRUE(db_.Execute("CREATE UNIQUE INDEX rows_index ON rows(indexed)"));
1303 // Populate the table with powers of two. These numbers make it easy to see if
1304 // SUM() missed a row.
1305 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(1, 1)"));
1306 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(2, 2)"));
1307 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(4, 4)"));
1308 ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(8, 8)"));
1311 ASSERT_TRUE(sql::test::CorruptIndexRootPage(db_path_, "rows_index"));
1312 ASSERT_TRUE(Reopen());
1314 // Run recovery code, then rollback. Database remains the same.
1316 std::unique_ptr<Recovery> recovery =
1317 Recovery::BeginRecoverDatabase(&db_, db_path_);
1318 ASSERT_TRUE(recovery);
1319 Recovery::Rollback(std::move(recovery));
1322 ASSERT_TRUE(Reopen());
1324 static const char kIndexedCountSql[] =
1325 "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
1327 sql::test::ScopedErrorExpecter expecter;
1328 expecter.ExpectError(SQLITE_CORRUPT);
1329 EXPECT_EQ("", ExecuteWithResult(&db_, kIndexedCountSql))
1330 << "Index should still be corrupted after recovery rollback";
1331 EXPECT_TRUE(expecter.SawExpectedErrors())
1332 << "Index should still be corrupted after recovery rollback";
1335 // Run recovery code, then commit. The index is recovered.
1337 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1338 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
1339 SqliteResultCode::kOk);
1341 std::unique_ptr<Recovery> recovery =
1342 Recovery::BeginRecoverDatabase(&db_, db_path_);
1343 ASSERT_TRUE(recovery);
1344 ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
1347 ASSERT_TRUE(Reopen());
1349 EXPECT_EQ("15", ExecuteWithResult(&db_, kIndexedCountSql))
1350 << "Index should be reconstructed after database recovery";
1353 TEST_P(SqlRecoveryTest, AttachFailure) {
1354 // Create a valid database, then write junk over the header. This should lead
1355 // to SQLITE_NOTADB, which will cause ATTACH to fail.
1356 ASSERT_TRUE(db_.Execute("CREATE TABLE x (t TEXT)"));
1357 ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test')"));
1359 ASSERT_TRUE(OverwriteDatabaseHeader());
1362 sql::test::ScopedErrorExpecter expecter;
1363 expecter.ExpectError(SQLITE_NOTADB);
1365 // Reopen() here because it will see SQLITE_NOTADB.
1366 ASSERT_TRUE(Reopen());
1368 // Begin() should fail.
1370 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1371 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
1372 SqliteResultCode::kNotADatabase);
1373 histogram_tester_.ExpectUniqueSample(
1374 kRecoveryResultHistogramName,
1375 BuiltInRecovery::Result::kFailedRecoveryRun,
1376 /*expected_bucket_count=*/1);
1377 histogram_tester_.ExpectUniqueSample(
1378 kRecoveryResultCodeHistogramName,
1379 SqliteLoggedResultCode::kNotADatabase,
1380 /*expected_bucket_count=*/1);
1382 std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
1383 EXPECT_FALSE(recovery.get());
1385 ASSERT_TRUE(expecter.SawExpectedErrors());
1389 // Helper for SqlRecoveryTest.PageSize. Creates a fresh db based on db_prefix,
1390 // with the given initial page size, and verifies it against the expected size.
1391 // Then changes to the final page size and recovers, verifying that the
1392 // recovered database ends up with the expected final page size.
1393 void TestPageSize(const base::FilePath& db_prefix,
1394 int initial_page_size,
1395 const std::string& expected_initial_page_size,
1396 int final_page_size,
1397 const std::string& expected_final_page_size,
1398 bool use_built_in) {
1399 static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
1400 static const char kInsertSql1[] = "INSERT INTO x VALUES ('This is a test')";
1401 static const char kInsertSql2[] = "INSERT INTO x VALUES ('That was a test')";
1402 static const char kSelectSql[] = "SELECT * FROM x ORDER BY t";
1404 const base::FilePath db_path = db_prefix.InsertBeforeExtensionASCII(
1405 base::NumberToString(initial_page_size));
1406 Database::Delete(db_path);
1407 Database db({.page_size = initial_page_size});
1408 ASSERT_TRUE(db.Open(db_path));
1409 ASSERT_TRUE(db.Execute(kCreateSql));
1410 ASSERT_TRUE(db.Execute(kInsertSql1));
1411 ASSERT_TRUE(db.Execute(kInsertSql2));
1412 ASSERT_EQ(expected_initial_page_size,
1413 ExecuteWithResult(&db, "PRAGMA page_size"));
1416 // Re-open the database while setting a new |options.page_size| in the object.
1417 Database recover_db({.page_size = final_page_size});
1418 ASSERT_TRUE(recover_db.Open(db_path));
1419 // Recovery will use the page size set in the database object, which may not
1420 // match the file's page size.
1422 EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1423 &recover_db, BuiltInRecovery::Strategy::kRecoverOrRaze),
1424 SqliteResultCode::kOk);
1426 Recovery::RecoverDatabase(&recover_db, db_path);
1429 // Recovery poisoned the handle, must re-open.
1432 // Make sure the page size is read from the file.
1433 Database recovered_db({.page_size = DatabaseOptions::kDefaultPageSize});
1434 ASSERT_TRUE(recovered_db.Open(db_path));
1435 ASSERT_EQ(expected_final_page_size,
1436 ExecuteWithResult(&recovered_db, "PRAGMA page_size"));
1437 EXPECT_EQ("That was a test\nThis is a test",
1438 ExecuteWithResults(&recovered_db, kSelectSql, "|", "\n"));
1441 // Verify that Recovery maintains the page size, and the virtual table
1442 // works with page sizes other than SQLite's default. Also verify the case
1443 // where the default page size has changed.
1444 TEST_P(SqlRecoveryTest, PageSize) {
1445 const std::string default_page_size =
1446 ExecuteWithResult(&db_, "PRAGMA page_size");
1448 // Check the default page size first.
1449 EXPECT_NO_FATAL_FAILURE(TestPageSize(
1450 db_path_, DatabaseOptions::kDefaultPageSize, default_page_size,
1451 DatabaseOptions::kDefaultPageSize, default_page_size, UseBuiltIn()));
1453 // Sync uses 32k pages.
1454 EXPECT_NO_FATAL_FAILURE(
1455 TestPageSize(db_path_, 32768, "32768", 32768, "32768", UseBuiltIn()));
1457 // Many clients use 4k pages. This is the SQLite default after 3.12.0.
1458 EXPECT_NO_FATAL_FAILURE(
1459 TestPageSize(db_path_, 4096, "4096", 4096, "4096", UseBuiltIn()));
1461 // 1k is the default page size before 3.12.0.
1462 EXPECT_NO_FATAL_FAILURE(
1463 TestPageSize(db_path_, 1024, "1024", 1024, "1024", UseBuiltIn()));
1465 ASSERT_NE("2048", default_page_size);
1467 // Databases with no page size specified should recover to the page size of
1468 // the source database.
1469 EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 2048, "2048",
1470 DatabaseOptions::kDefaultPageSize,
1471 "2048", UseBuiltIn()));
1473 // Databases with no page size specified should recover with the new default
1474 // page size. 2k has never been the default page size.
1475 EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 2048, "2048",
1476 DatabaseOptions::kDefaultPageSize,
1477 default_page_size, UseBuiltIn()));
1481 TEST_P(SqlRecoveryTest, CannotRecoverClosedDb) {
1485 EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
1486 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze));
1488 EXPECT_DCHECK_DEATH(Recovery::RecoverDatabase(&db_, db_path_));
1492 TEST_P(SqlRecoveryTest, CannotRecoverDbWithErrorCallback) {
1493 db_.set_error_callback(base::DoNothing());
1496 EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
1497 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze));
1499 EXPECT_DCHECK_DEATH(Recovery::RecoverDatabase(&db_, db_path_));
1503 #if !BUILDFLAG(IS_FUCHSIA)
1504 // TODO(https://crbug.com/1255316): Ideally this would be a
1505 // `SqlRecoveryTest`, but `Recovery::RecoverDatabase()` does not DCHECK
1506 // that it is passed a non-null database pointer and will instead likely result
1507 // in unexpected behavior or crashes.
1508 TEST_F(SqlBuiltInRecoveryTest, CannotRecoverNullDb) {
1509 EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
1510 nullptr, BuiltInRecovery::Strategy::kRecoverOrRaze));
1513 // TODO(https://crbug.com/1255316): Ideally this would be a
1514 // `SqlRecoveryTest`, but `Recovery::RecoverDatabase()` does not DCHECK
1515 // whether the database is in-memory and will instead likely result in
1516 // unexpected behavior or crashes.
1517 TEST_F(SqlBuiltInRecoveryTest, CannotRecoverInMemoryDb) {
1518 Database in_memory_db;
1519 ASSERT_TRUE(in_memory_db.OpenInMemory());
1522 std::ignore = BuiltInRecovery::RecoverDatabase(
1523 &in_memory_db, BuiltInRecovery::Strategy::kRecoverOrRaze));
1526 TEST_P(SqlRecoveryTest, BuiltInRecoveryNotAttempedIfNotEnabled) {
1527 // `BuiltInRecovery` will return early if the kill switch is disabled.
1529 base::FeatureList::IsEnabled(features::kUseBuiltInRecoveryIfSupported),
1530 IsSqliteSuccessCode(BuiltInRecovery::RecoverDatabase(
1531 &db_, BuiltInRecovery::Strategy::kRecoverOrRaze)));
1533 #endif // !BUILDFLAG(IS_FUCHSIA)
1535 INSTANTIATE_TEST_SUITE_P(All, SqlRecoveryTest, testing::Bool());