diff options
author | Andras Becsi <andras.becsi@digia.com> | 2014-03-18 13:16:26 +0100 |
---|---|---|
committer | Frederik Gladhorn <frederik.gladhorn@digia.com> | 2014-03-20 15:55:39 +0100 |
commit | 3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch) | |
tree | 92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/sql/recovery_unittest.cc | |
parent | e90d7c4b152c56919d963987e2503f9909a666d2 (diff) | |
download | qtwebengine-chromium-3f0f86b0caed75241fa71c95a5d73bc0164348c5.tar.gz |
Update to new stable branch 1750
This also includes an updated ninja and chromium dependencies
needed on Windows.
Change-Id: Icd597d80ed3fa4425933c9f1334c3c2e31291c42
Reviewed-by: Zoltan Arvai <zarvai@inf.u-szeged.hu>
Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
Diffstat (limited to 'chromium/sql/recovery_unittest.cc')
-rw-r--r-- | chromium/sql/recovery_unittest.cc | 421 |
1 files changed, 342 insertions, 79 deletions
diff --git a/chromium/sql/recovery_unittest.cc b/chromium/sql/recovery_unittest.cc index fc7c2f2dc5b..ce3884bbc08 100644 --- a/chromium/sql/recovery_unittest.cc +++ b/chromium/sql/recovery_unittest.cc @@ -2,16 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <string> + #include "base/bind.h" -#include "base/file_util.h" +#include "base/files/file_path.h" #include "base/files/scoped_temp_dir.h" -#include "base/logging.h" -#include "base/strings/stringprintf.h" +#include "base/strings/string_number_conversions.h" #include "sql/connection.h" #include "sql/meta_table.h" #include "sql/recovery.h" #include "sql/statement.h" #include "sql/test/scoped_error_ignorer.h" +#include "sql/test/test_helpers.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" @@ -32,7 +34,15 @@ std::string ExecuteWithResults(sql::Connection* db, for (int i = 0; i < s.ColumnCount(); ++i) { if (i > 0) ret += column_sep; - ret += s.ColumnString(i); + if (s.ColumnType(i) == sql::COLUMN_TYPE_NULL) { + ret += "<null>"; + } else if (s.ColumnType(i) == sql::COLUMN_TYPE_BLOB) { + ret += "<x'"; + ret += base::HexEncode(s.ColumnBlob(i), s.ColumnByteLength(i)); + ret += "'>"; + } else { + ret += s.ColumnString(i); + } } } return ret; @@ -48,49 +58,6 @@ std::string GetSchema(sql::Connection* db) { return ExecuteWithResults(db, kSql, "|", "\n"); } -int GetPageSize(sql::Connection* db) { - sql::Statement s(db->GetUniqueStatement("PRAGMA page_size")); - EXPECT_TRUE(s.Step()); - return s.ColumnInt(0); -} - -// Get |name|'s root page number in the database. -int GetRootPage(sql::Connection* db, const char* name) { - const char kPageSql[] = "SELECT rootpage FROM sqlite_master WHERE name = ?"; - sql::Statement s(db->GetUniqueStatement(kPageSql)); - s.BindString(0, name); - EXPECT_TRUE(s.Step()); - return s.ColumnInt(0); -} - -// Helper to read a SQLite page into a buffer. |page_no| is 1-based -// per SQLite usage. -bool ReadPage(const base::FilePath& path, size_t page_no, - char* buf, size_t page_size) { - file_util::ScopedFILE file(file_util::OpenFile(path, "rb")); - if (!file.get()) - return false; - if (0 != fseek(file.get(), (page_no - 1) * page_size, SEEK_SET)) - return false; - if (1u != fread(buf, page_size, 1, file.get())) - return false; - return true; -} - -// Helper to write a SQLite page into a buffer. |page_no| is 1-based -// per SQLite usage. -bool WritePage(const base::FilePath& path, size_t page_no, - const char* buf, size_t page_size) { - file_util::ScopedFILE file(file_util::OpenFile(path, "rb+")); - if (!file.get()) - return false; - if (0 != fseek(file.get(), (page_no - 1) * page_size, SEEK_SET)) - return false; - if (1u != fwrite(buf, page_size, 1, file.get())) - return false; - return true; -} - class SQLRecoveryTest : public testing::Test { public: SQLRecoveryTest() {} @@ -285,24 +252,12 @@ TEST_F(SQLRecoveryTest, RecoverCorruptIndex) { ASSERT_TRUE(db().CommitTransaction()); } - - - // Capture the index's root page into |buf|. - int index_page = GetRootPage(&db(), "x_id"); - int page_size = GetPageSize(&db()); - scoped_ptr<char[]> buf(new char[page_size]); - ASSERT_TRUE(ReadPage(db_path(), index_page, buf.get(), page_size)); - - // Delete the row from the table and index. - ASSERT_TRUE(db().Execute("DELETE FROM x WHERE id = 0")); - - // Close to clear any cached data. db().Close(); - // Put the stale index page back. - ASSERT_TRUE(WritePage(db_path(), index_page, buf.get(), page_size)); - - // At this point, the index references a value not in the table. + // Delete a row from the table, while leaving the index entry which + // references it. + const char kDeleteSql[] = "DELETE FROM x WHERE id = 0"; + ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x_id", kDeleteSql)); ASSERT_TRUE(Reopen()); @@ -357,25 +312,12 @@ TEST_F(SQLRecoveryTest, RecoverCorruptTable) { ASSERT_TRUE(db().CommitTransaction()); } - - // Capture the table's root page into |buf|. - // Find the page the table is stored on. - const int table_page = GetRootPage(&db(), "x"); - const int page_size = GetPageSize(&db()); - scoped_ptr<char[]> buf(new char[page_size]); - ASSERT_TRUE(ReadPage(db_path(), table_page, buf.get(), page_size)); - - // Delete the row from the table and index. - ASSERT_TRUE(db().Execute("DELETE FROM x WHERE id = 0")); - - // Close to clear any cached data. db().Close(); - // Put the stale table page back. - ASSERT_TRUE(WritePage(db_path(), table_page, buf.get(), page_size)); + // Delete a row from the index while leaving a table entry. + const char kDeleteSql[] = "DELETE FROM x WHERE id = 0"; + ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x", kDeleteSql)); - // At this point, the table contains a value not referenced by the - // index. // TODO(shess): Figure out a query which causes SQLite to notice // this organically. Meanwhile, just handle it manually. @@ -420,6 +362,327 @@ TEST_F(SQLRecoveryTest, RecoverCorruptTable) { const char kSelectSql[] = "SELECT v FROM x WHERE id = 0"; EXPECT_EQ("100", ExecuteWithResults(&db(), kSelectSql, "|", ",")); } + +TEST_F(SQLRecoveryTest, Meta) { + const int kVersion = 3; + const int kCompatibleVersion = 2; + + { + sql::MetaTable meta; + EXPECT_TRUE(meta.Init(&db(), kVersion, kCompatibleVersion)); + EXPECT_EQ(kVersion, meta.GetVersionNumber()); + } + + // Test expected case where everything works. + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + EXPECT_TRUE(recovery->SetupMeta()); + int version = 0; + EXPECT_TRUE(recovery->GetMetaVersionNumber(&version)); + EXPECT_EQ(kVersion, version); + + sql::Recovery::Rollback(recovery.Pass()); + } + ASSERT_TRUE(Reopen()); // Handle was poisoned. + + // Test version row missing. + EXPECT_TRUE(db().Execute("DELETE FROM meta WHERE key = 'version'")); + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + EXPECT_TRUE(recovery->SetupMeta()); + int version = 0; + EXPECT_FALSE(recovery->GetMetaVersionNumber(&version)); + EXPECT_EQ(0, version); + + sql::Recovery::Rollback(recovery.Pass()); + } + ASSERT_TRUE(Reopen()); // Handle was poisoned. + + // Test meta table missing. + EXPECT_TRUE(db().Execute("DROP TABLE meta")); + { + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_CORRUPT); // From virtual table. + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + EXPECT_FALSE(recovery->SetupMeta()); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + } +} + +// Baseline AutoRecoverTable() test. +TEST_F(SQLRecoveryTest, AutoRecoverTable) { + // BIGINT and VARCHAR to test type affinity. + const char kCreateSql[] = "CREATE TABLE x (id BIGINT, t TEXT, v VARCHAR)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (11, 'This is', 'a test')")); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, 'That was', 'a test')")); + + // Save aside a copy of the original schema and data. + const std::string orig_schema(GetSchema(&db())); + const char kXSql[] = "SELECT * FROM x ORDER BY 1"; + const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); + + // Create a lame-duck table which will not be propagated by recovery to + // detect that the recovery code actually ran. + ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); + ASSERT_NE(orig_schema, GetSchema(&db())); + + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); + + // Save a copy of the temp db's schema before recovering the table. + const char kTempSchemaSql[] = "SELECT name, sql FROM sqlite_temp_master"; + const std::string temp_schema( + ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n")); + + size_t rows = 0; + EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); + EXPECT_EQ(2u, rows); + + // Test that any additional temp tables were cleaned up. + EXPECT_EQ(temp_schema, + ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n")); + + ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); + } + + // Since the database was not corrupt, the entire schema and all + // data should be recovered. + ASSERT_TRUE(Reopen()); + ASSERT_EQ(orig_schema, GetSchema(&db())); + ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); + + // Recovery fails if the target table doesn't exist. + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); + + // TODO(shess): Should this failure implicitly lead to Raze()? + size_t rows = 0; + EXPECT_FALSE(recovery->AutoRecoverTable("y", 0, &rows)); + + sql::Recovery::Unrecoverable(recovery.Pass()); + } +} + +// Test that default values correctly replace nulls. The recovery +// virtual table reads directly from the database, so DEFAULT is not +// interpretted at that level. +TEST_F(SQLRecoveryTest, AutoRecoverTableWithDefault) { + ASSERT_TRUE(db().Execute("CREATE TABLE x (id INTEGER)")); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5)")); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15)")); + + // ALTER effectively leaves the new columns NULL in the first two + // rows. The row with 17 will get the default injected at insert + // time, while the row with 42 will get the actual value provided. + // Embedded "'" to make sure default-handling continues to be quoted + // correctly. + ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN t TEXT DEFAULT 'a''a'")); + ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN b BLOB DEFAULT x'AA55'")); + ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN i INT DEFAULT 93")); + ASSERT_TRUE(db().Execute("INSERT INTO x (id) VALUES (17)")); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (42, 'b', x'1234', 12)")); + + // Save aside a copy of the original schema and data. + const std::string orig_schema(GetSchema(&db())); + const char kXSql[] = "SELECT * FROM x ORDER BY 1"; + const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); + + // Create a lame-duck table which will not be propagated by recovery to + // detect that the recovery code actually ran. + ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); + ASSERT_NE(orig_schema, GetSchema(&db())); + + // Mechanically adjust the stored schema and data to allow detecting + // where the default value is coming from. The target table is just + // like the original with the default for [t] changed, to signal + // defaults coming from the recovery system. The two %5 rows should + // get the target-table default for [t], while the others should get + // the source-table default. + std::string final_schema(orig_schema); + std::string final_data(orig_data); + size_t pos; + while ((pos = final_schema.find("'a''a'")) != std::string::npos) { + final_schema.replace(pos, 6, "'c''c'"); + } + while ((pos = final_data.find("5|a'a")) != std::string::npos) { + final_data.replace(pos, 5, "5|c'c"); + } + + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + // Different default to detect which table provides the default. + ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str())); + + size_t rows = 0; + EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); + EXPECT_EQ(4u, rows); + + ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); + } + + // Since the database was not corrupt, the entire schema and all + // data should be recovered. + ASSERT_TRUE(Reopen()); + ASSERT_EQ(final_schema, GetSchema(&db())); + ASSERT_EQ(final_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); +} + +// Test that rows with NULL in a NOT NULL column are filtered +// correctly. In the wild, this would probably happen due to +// corruption, but here it is simulated by recovering a table which +// allowed nulls into a table which does not. +TEST_F(SQLRecoveryTest, AutoRecoverTableNullFilter) { + const char kOrigSchema[] = "CREATE TABLE x (id INTEGER, t TEXT)"; + const char kFinalSchema[] = "CREATE TABLE x (id INTEGER, t TEXT NOT NULL)"; + + ASSERT_TRUE(db().Execute(kOrigSchema)); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, null)")); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15, 'this is a test')")); + + // Create a lame-duck table which will not be propagated by recovery to + // detect that the recovery code actually ran. + ASSERT_EQ(kOrigSchema, GetSchema(&db())); + ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); + ASSERT_NE(kOrigSchema, GetSchema(&db())); + + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + ASSERT_TRUE(recovery->db()->Execute(kFinalSchema)); + + size_t rows = 0; + EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); + EXPECT_EQ(1u, rows); + + ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); + } + + // The schema should be the same, but only one row of data should + // have been recovered. + ASSERT_TRUE(Reopen()); + ASSERT_EQ(kFinalSchema, GetSchema(&db())); + const char kXSql[] = "SELECT * FROM x ORDER BY 1"; + ASSERT_EQ("15|this is a test", ExecuteWithResults(&db(), kXSql, "|", "\n")); +} + +// Test AutoRecoverTable with a ROWID alias. +TEST_F(SQLRecoveryTest, AutoRecoverTableWithRowid) { + // The rowid alias is almost always the first column, intentionally + // put it later. + const char kCreateSql[] = + "CREATE TABLE x (t TEXT, id INTEGER PRIMARY KEY NOT NULL)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test', null)")); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test', null)")); + + // Save aside a copy of the original schema and data. + const std::string orig_schema(GetSchema(&db())); + const char kXSql[] = "SELECT * FROM x ORDER BY 1"; + const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); + + // Create a lame-duck table which will not be propagated by recovery to + // detect that the recovery code actually ran. + ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); + ASSERT_NE(orig_schema, GetSchema(&db())); + + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); + + size_t rows = 0; + EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); + EXPECT_EQ(2u, rows); + + ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); + } + + // Since the database was not corrupt, the entire schema and all + // data should be recovered. + ASSERT_TRUE(Reopen()); + ASSERT_EQ(orig_schema, GetSchema(&db())); + ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); +} + +// Test that a compound primary key doesn't fire the ROWID code. +TEST_F(SQLRecoveryTest, AutoRecoverTableWithCompoundKey) { + const char kCreateSql[] = + "CREATE TABLE x (" + "id INTEGER NOT NULL," + "id2 TEXT NOT NULL," + "t TEXT," + "PRIMARY KEY (id, id2)" + ")"; + ASSERT_TRUE(db().Execute(kCreateSql)); + + // NOTE(shess): Do not accidentally use [id] 1, 2, 3, as those will + // be the ROWID values. + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'a', 'This is a test')")); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'b', 'That was a test')")); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'a', 'Another test')")); + + // Save aside a copy of the original schema and data. + const std::string orig_schema(GetSchema(&db())); + const char kXSql[] = "SELECT * FROM x ORDER BY 1"; + const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); + + // Create a lame-duck table which will not be propagated by recovery to + // detect that the recovery code actually ran. + ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); + ASSERT_NE(orig_schema, GetSchema(&db())); + + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); + + size_t rows = 0; + EXPECT_TRUE(recovery->AutoRecoverTable("x", 0, &rows)); + EXPECT_EQ(3u, rows); + + ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); + } + + // Since the database was not corrupt, the entire schema and all + // data should be recovered. + ASSERT_TRUE(Reopen()); + ASSERT_EQ(orig_schema, GetSchema(&db())); + ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); +} + +// Test |extend_columns| support. +TEST_F(SQLRecoveryTest, AutoRecoverTableExtendColumns) { + const char kCreateSql[] = "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'This is')")); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'That was')")); + + // Save aside a copy of the original schema and data. + const std::string orig_schema(GetSchema(&db())); + const char kXSql[] = "SELECT * FROM x ORDER BY 1"; + const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); + + // Modify the table to add a column, and add data to that column. + ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN t1 TEXT")); + ASSERT_TRUE(db().Execute("UPDATE x SET t1 = 'a test'")); + ASSERT_NE(orig_schema, GetSchema(&db())); + ASSERT_NE(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); + + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); + size_t rows = 0; + EXPECT_TRUE(recovery->AutoRecoverTable("x", 1, &rows)); + EXPECT_EQ(2u, rows); + ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); + } + + // Since the database was not corrupt, the entire schema and all + // data should be recovered. + ASSERT_TRUE(Reopen()); + ASSERT_EQ(orig_schema, GetSchema(&db())); + ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); +} #endif // !defined(USE_SYSTEM_SQLITE) } // namespace |