summaryrefslogtreecommitdiffstats
path: root/sql/test
diff options
context:
space:
mode:
authorshess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-08 06:49:12 +0000
committershess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-08 06:49:12 +0000
commitae4f1625a8a4d8ea9bde7f4a107d60814b12e593 (patch)
tree24be90bb9b4b1c2f422ef0aac1da515b68e0753c /sql/test
parent8837674f3446a46adc28e4c73444c4fc8689dc15 (diff)
downloadchromium_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.cc84
-rw-r--r--sql/test/test_helpers.h21
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;