diff options
author | shess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-08 06:49:12 +0000 |
---|---|---|
committer | shess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-08 06:49:12 +0000 |
commit | ae4f1625a8a4d8ea9bde7f4a107d60814b12e593 (patch) | |
tree | 24be90bb9b4b1c2f422ef0aac1da515b68e0753c /sql/test | |
parent | 8837674f3446a46adc28e4c73444c4fc8689dc15 (diff) | |
download | chromium_src-ae4f1625a8a4d8ea9bde7f4a107d60814b12e593.zip chromium_src-ae4f1625a8a4d8ea9bde7f4a107d60814b12e593.tar.gz chromium_src-ae4f1625a8a4d8ea9bde7f4a107d60814b12e593.tar.bz2 |
[sql] Recovery code for "Top Sites" database.
Port recovery code from thumbnail_database.cc to
top_sites_database.cc. Also retry Init() in case the recovery code
fixes things (a quarter to a third of databases have problems detected
during open).
Extend sql::Recovery::AutoRecoverTable() to handle DOUBLE columns.
Abstract out sql::test::CorruptTableOrIndex() to ease writing
corruption tests.
IWYU pass on various files.
BUG=321852
Review URL: https://codereview.chromium.org/86653002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@239377 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sql/test')
-rw-r--r-- | sql/test/test_helpers.cc | 84 | ||||
-rw-r--r-- | sql/test/test_helpers.h | 21 |
2 files changed, 105 insertions, 0 deletions
diff --git a/sql/test/test_helpers.cc b/sql/test/test_helpers.cc index 6e12b04..8d56140 100644 --- a/sql/test/test_helpers.cc +++ b/sql/test/test_helpers.cc @@ -21,6 +21,26 @@ size_t CountSQLItemsOfType(sql::Connection* db, const char* type) { return s.ColumnInt(0); } +// Get page size for the database. +bool GetPageSize(sql::Connection* db, int* page_size) { + sql::Statement s(db->GetUniqueStatement("PRAGMA page_size")); + if (!s.Step()) + return false; + *page_size = s.ColumnInt(0); + return true; +} + +// Get |name|'s root page number in the database. +bool GetRootPage(sql::Connection* db, const char* name, int* page_number) { + const char kPageSql[] = "SELECT rootpage FROM sqlite_master WHERE name = ?"; + sql::Statement s(db->GetUniqueStatement(kPageSql)); + s.BindString(0, name); + if (!s.Step()) + return false; + *page_number = s.ColumnInt(0); + return true; +} + // Helper for reading a number from the SQLite header. // See net/base/big_endian.h. unsigned ReadBigEndian(unsigned char* buf, size_t bytes) { @@ -88,6 +108,70 @@ bool CorruptSizeInHeader(const base::FilePath& db_path) { return true; } +bool CorruptTableOrIndex(const base::FilePath& db_path, + const char* tree_name, + const char* update_sql) { + sql::Connection db; + if (!db.Open(db_path)) + return false; + + int page_size = 0; + if (!GetPageSize(&db, &page_size)) + return false; + + int page_number = 0; + if (!GetRootPage(&db, tree_name, &page_number)) + return false; + + // SQLite uses 1-based page numbering. + const long int page_ofs = (page_number - 1) * page_size; + scoped_ptr<char[]> page_buf(new char[page_size]); + + // Get the page into page_buf. + file_util::ScopedFILE file(base::OpenFile(db_path, "rb+")); + if (!file.get()) + return false; + if (0 != fseek(file.get(), page_ofs, SEEK_SET)) + return false; + if (1u != fread(page_buf.get(), page_size, 1, file.get())) + return false; + + // Require the page to be a leaf node. A multilevel tree would be + // very hard to restore correctly. + if (page_buf[0] != 0xD && page_buf[0] != 0xA) + return false; + + // The update has to work, and make changes. + if (!db.Execute(update_sql)) + return false; + if (db.GetLastChangeCount() == 0) + return false; + + // Ensure that the database is fully flushed. + db.Close(); + + // Check that the stored page actually changed. This catches usage + // errors where |update_sql| is not related to |tree_name|. + scoped_ptr<char[]> check_page_buf(new char[page_size]); + // The on-disk data should have changed. + if (0 != fflush(file.get())) + return false; + if (0 != fseek(file.get(), page_ofs, SEEK_SET)) + return false; + if (1u != fread(check_page_buf.get(), page_size, 1, file.get())) + return false; + if (!memcmp(check_page_buf.get(), page_buf.get(), page_size)) + return false; + + // Put the original page back. + if (0 != fseek(file.get(), page_ofs, SEEK_SET)) + return false; + if (1u != fwrite(page_buf.get(), page_size, 1, file.get())) + return false; + + return true; +} + size_t CountSQLTables(sql::Connection* db) { return CountSQLItemsOfType(db, "table"); } diff --git a/sql/test/test_helpers.h b/sql/test/test_helpers.h index b9d5e9b..d9dda22 100644 --- a/sql/test/test_helpers.h +++ b/sql/test/test_helpers.h @@ -9,6 +9,7 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "base/files/file_path.h" // Collection of test-only convenience functions. @@ -33,6 +34,26 @@ namespace test { // Returns false if any error occurs accessing the file. bool CorruptSizeInHeader(const base::FilePath& db_path) WARN_UNUSED_RESULT; +// Frequently corruption is a result of failure to atomically update +// pages in different structures. For instance, if an index update +// takes effect but the corresponding table update does not. This +// helper restores the prior version of a b-tree root after running an +// update which changed that b-tree. The named b-tree must exist and +// must be a leaf node (either index or table). Returns true if the +// on-disk file is successfully modified, and the restored page +// differs from the updated page. +// +// The resulting database should be possible to open, and many +// statements should work. SQLITE_CORRUPT will be thrown if a query +// through the index finds the row missing in the table. +// +// TODO(shess): It would be very helpful to allow a parameter to the +// sql statement. Perhaps a version with a string parameter would be +// sufficient, given affinity rules? +bool CorruptTableOrIndex(const base::FilePath& db_path, + const char* tree_name, + const char* update_sql) WARN_UNUSED_RESULT; + // Return the number of tables in sqlite_master. size_t CountSQLTables(sql::Connection* db) WARN_UNUSED_RESULT; |