diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-03-08 10:28:10 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-03-20 13:40:30 +0000 |
commit | e733310db58160074f574c429d48f8308c0afe17 (patch) | |
tree | f8aef4b7e62a69928dbcf880620eece20f98c6df /chromium/sql | |
parent | 2f583e4aec1ae3a86fa047829c96b310dc12ecdf (diff) | |
download | qtwebengine-chromium-e733310db58160074f574c429d48f8308c0afe17.tar.gz |
BASELINE: Update Chromium to 56.0.2924.122
Change-Id: I4e04de8f47e47e501c46ed934c76a431c6337ced
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/sql')
-rw-r--r-- | chromium/sql/connection.cc | 109 | ||||
-rw-r--r-- | chromium/sql/connection.h | 29 | ||||
-rw-r--r-- | chromium/sql/connection_unittest.cc | 220 | ||||
-rw-r--r-- | chromium/sql/meta_table.cc | 2 | ||||
-rw-r--r-- | chromium/sql/recovery_unittest.cc | 121 | ||||
-rw-r--r-- | chromium/sql/sqlite_features_unittest.cc | 33 |
6 files changed, 391 insertions, 123 deletions
diff --git a/chromium/sql/connection.cc b/chromium/sql/connection.cc index 073b032cde6..788329bafd3 100644 --- a/chromium/sql/connection.cc +++ b/chromium/sql/connection.cc @@ -318,6 +318,7 @@ Connection::Connection() needs_rollback_(false), in_memory_(false), poisoned_(false), + mmap_alt_status_(false), mmap_disabled_(false), mmap_enabled_(false), total_changes_at_last_release_(0), @@ -840,6 +841,49 @@ std::string Connection::CollectCorruptionInfo() { return debug_info; } +bool Connection::GetMmapAltStatus(int64_t* status) { + // The [meta] version uses a missing table as a signal for a fresh database. + // That will not work for the view, which would not exist in either a new or + // an existing database. A new database _should_ be only one page long, so + // just don't bother optimizing this case (start at offset 0). + // TODO(shess): Could the [meta] case also get simpler, then? + if (!DoesViewExist("MmapStatus")) { + *status = 0; + return true; + } + + const char* kMmapStatusSql = "SELECT * FROM MmapStatus"; + Statement s(GetUniqueStatement(kMmapStatusSql)); + if (s.Step()) + *status = s.ColumnInt64(0); + return s.Succeeded(); +} + +bool Connection::SetMmapAltStatus(int64_t status) { + if (!BeginTransaction()) + return false; + + // View may not exist on first run. + if (!Execute("DROP VIEW IF EXISTS MmapStatus")) { + RollbackTransaction(); + return false; + } + + // Views live in the schema, so they cannot be parameterized. For an integer + // value, this construct should be safe from SQL injection, if the value + // becomes more complicated use "SELECT quote(?)" to generate a safe quoted + // value. + const std::string createViewSql = + base::StringPrintf("CREATE VIEW MmapStatus (value) AS SELECT %" PRId64, + status); + if (!Execute(createViewSql.c_str())) { + RollbackTransaction(); + return false; + } + + return CommitTransaction(); +} + size_t Connection::GetAppropriateMmapSize() { AssertIOAllowed(); @@ -854,27 +898,27 @@ size_t Connection::GetAppropriateMmapSize() { // percentile of Chrome databases in the wild, so this should be good. const size_t kMmapEverything = 256 * 1024 * 1024; - // If the database doesn't have a place to track progress, assume the best. - // This will happen when new databases are created, or if a database doesn't - // use a meta table. sql::MetaTable::Init() will preload kMmapSuccess. - // TODO(shess): Databases not using meta include: - // DOMStorageDatabase (localstorage) - // ActivityDatabase (extensions activity log) - // PredictorDatabase (prefetch and autocomplete predictor data) - // SyncDirectory (sync metadata storage) - // For now, these all have mmap disabled to allow other databases to get the - // default-enable path. sqlite-diag could be an alternative for all but - // DOMStorageDatabase, which creates many small databases. - // http://crbug.com/537742 - if (!MetaTable::DoesTableExist(this)) { - RecordOneEvent(EVENT_MMAP_META_MISSING); - return kMmapEverything; - } - + // Progress information is tracked in the [meta] table for databases which use + // sql::MetaTable, otherwise it is tracked in a special view. + // TODO(shess): Move all cases to the view implementation. int64_t mmap_ofs = 0; - if (!MetaTable::GetMmapStatus(this, &mmap_ofs)) { - RecordOneEvent(EVENT_MMAP_META_FAILURE_READ); - return 0; + if (mmap_alt_status_) { + if (!GetMmapAltStatus(&mmap_ofs)) { + RecordOneEvent(EVENT_MMAP_STATUS_FAILURE_READ); + return 0; + } + } else { + // If [meta] doesn't exist, yet, it's a new database, assume the best. + // sql::MetaTable::Init() will preload kMmapSuccess. + if (!MetaTable::DoesTableExist(this)) { + RecordOneEvent(EVENT_MMAP_META_MISSING); + return kMmapEverything; + } + + if (!MetaTable::GetMmapStatus(this, &mmap_ofs)) { + RecordOneEvent(EVENT_MMAP_META_FAILURE_READ); + return 0; + } } // Database read failed in the past, don't memory map. @@ -949,9 +993,16 @@ size_t Connection::GetAppropriateMmapSize() { event = EVENT_MMAP_FAILED_NEW; } - if (!MetaTable::SetMmapStatus(this, mmap_ofs)) { - RecordOneEvent(EVENT_MMAP_META_FAILURE_UPDATE); - return 0; + if (mmap_alt_status_) { + if (!SetMmapAltStatus(mmap_ofs)) { + RecordOneEvent(EVENT_MMAP_STATUS_FAILURE_UPDATE); + return 0; + } + } else { + if (!MetaTable::SetMmapStatus(this, mmap_ofs)) { + RecordOneEvent(EVENT_MMAP_META_FAILURE_UPDATE); + return 0; + } } RecordOneEvent(event); @@ -1503,15 +1554,19 @@ bool Connection::IsSQLValid(const char* sql) { return true; } +bool Connection::DoesIndexExist(const char* index_name) const { + return DoesSchemaItemExist(index_name, "index"); +} + bool Connection::DoesTableExist(const char* table_name) const { - return DoesTableOrIndexExist(table_name, "table"); + return DoesSchemaItemExist(table_name, "table"); } -bool Connection::DoesIndexExist(const char* index_name) const { - return DoesTableOrIndexExist(index_name, "index"); +bool Connection::DoesViewExist(const char* view_name) const { + return DoesSchemaItemExist(view_name, "view"); } -bool Connection::DoesTableOrIndexExist( +bool Connection::DoesSchemaItemExist( const char* name, const char* type) const { const char* kSql = "SELECT name FROM sqlite_master WHERE type=? AND name=? COLLATE NOCASE"; diff --git a/chromium/sql/connection.h b/chromium/sql/connection.h index 5b8d39964ab..0fb251dd8cc 100644 --- a/chromium/sql/connection.h +++ b/chromium/sql/connection.h @@ -150,6 +150,11 @@ class SQL_EXPORT Connection { // other platforms. void set_restrict_to_user() { restrict_to_user_ = true; } + // Call to use alternative status-tracking for mmap. Usually this is tracked + // in the meta table, but some databases have no meta table. + // TODO(shess): Maybe just have all databases use the alt option? + void set_mmap_alt_status() { mmap_alt_status_ = true; } + // Call to opt out of memory-mapped file I/O. void set_mmap_disabled() { mmap_disabled_ = true; } @@ -219,6 +224,9 @@ class SQL_EXPORT Connection { EVENT_MMAP_SUCCESS_PARTIAL, // Read but did not reach EOF. EVENT_MMAP_SUCCESS_NO_PROGRESS, // Read quota exhausted. + EVENT_MMAP_STATUS_FAILURE_READ, // Failure reading MmapStatus view. + EVENT_MMAP_STATUS_FAILURE_UPDATE,// Failure updating MmapStatus view. + // Leave this at the end. // TODO(shess): |EVENT_MAX| causes compile fail on Windows. EVENT_MAX_VALUE @@ -443,11 +451,12 @@ class SQL_EXPORT Connection { // Info querying ------------------------------------------------------------- - // Returns true if the given table (or index) exists. Instead of - // test-then-create, callers should almost always prefer "CREATE TABLE IF NOT - // EXISTS" or "CREATE INDEX IF NOT EXISTS". - bool DoesTableExist(const char* table_name) const; + // Returns true if the given structure exists. Instead of test-then-create, + // callers should almost always prefer the "IF NOT EXISTS" version of the + // CREATE statement. bool DoesIndexExist(const char* index_name) const; + bool DoesTableExist(const char* table_name) const; + bool DoesViewExist(const char* table_name) const; // Returns true if a column with the given name exists in the given table. bool DoesColumnExist(const char* table_name, const char* column_name) const; @@ -508,6 +517,7 @@ class SQL_EXPORT Connection { FRIEND_TEST_ALL_PREFIXES(SQLConnectionTest, CollectDiagnosticInfo); FRIEND_TEST_ALL_PREFIXES(SQLConnectionTest, GetAppropriateMmapSize); + FRIEND_TEST_ALL_PREFIXES(SQLConnectionTest, GetAppropriateMmapSizeAltStatus); FRIEND_TEST_ALL_PREFIXES(SQLConnectionTest, OnMemoryDump); FRIEND_TEST_ALL_PREFIXES(SQLConnectionTest, RegisterIntentToUpload); @@ -535,8 +545,8 @@ class SQL_EXPORT Connection { base::ThreadRestrictions::AssertIOAllowed(); } - // Internal helper for DoesTableExist and DoesIndexExist. - bool DoesTableOrIndexExist(const char* name, const char* type) const; + // Internal helper for Does*Exist() functions. + bool DoesSchemaItemExist(const char* name, const char* type) const; // Accessors for global error-expecter, for injecting behavior during tests. // See test/scoped_error_expecter.h. @@ -722,6 +732,10 @@ class SQL_EXPORT Connection { // the file should only be read through once. size_t GetAppropriateMmapSize(); + // Helpers for GetAppropriateMmapSize(). + bool GetMmapAltStatus(int64_t* status); + bool SetMmapAltStatus(int64_t status); + // The actual sqlite database. Will be NULL before Init has been called or if // Init resulted in an error. sqlite3* db_; @@ -763,6 +777,9 @@ class SQL_EXPORT Connection { // databases. bool poisoned_; + // |true| to use alternate storage for tracking mmap status. + bool mmap_alt_status_; + // |true| if SQLite memory-mapped I/O is not desired for this connection. bool mmap_disabled_; diff --git a/chromium/sql/connection_unittest.cc b/chromium/sql/connection_unittest.cc index 6b2d4e75864..a08456e36a8 100644 --- a/chromium/sql/connection_unittest.cc +++ b/chromium/sql/connection_unittest.cc @@ -11,7 +11,7 @@ #include "base/files/scoped_temp_dir.h" #include "base/logging.h" #include "base/macros.h" -#include "base/metrics/statistics_recorder.h" +#include "base/strings/string_number_conversions.h" #include "base/test/histogram_tester.h" #include "base/trace_event/process_memory_dump.h" #include "sql/connection.h" @@ -146,6 +146,9 @@ class ScopedCommitHook { namespace { +using sql::test::ExecuteWithResults; +using sql::test::ExecuteWithResult; + // Helper to return the count of items in sqlite_master. Return -1 in // case of error. int SqliteMasterCount(sql::Connection* db) { @@ -201,6 +204,17 @@ void ErrorCallbackResetHelper(sql::Connection* db, EXPECT_GT(*counter, 0u); } +// Handle errors by blowing away the database. +void RazeErrorCallback(sql::Connection* db, + int expected_error, + int error, + sql::Statement* stmt) { + // Nothing here needs extended errors at this time. + EXPECT_EQ(expected_error, expected_error&0xff); + EXPECT_EQ(expected_error, error&0xff); + db->RazeAndClose(); +} + #if defined(OS_POSIX) // Set a umask and restore the old mask on destruction. Cribbed from // shared_memory_unittest.cc. Used by POSIX-only UserPermission test. @@ -239,24 +253,7 @@ const char kQueryTime[] = "Sqlite.QueryTime.Test"; } // namespace -class SQLConnectionTest : public sql::SQLTestBase { - public: - void SetUp() override { - // Any macro histograms which fire before the recorder is initialized cannot - // be tested. So this needs to be ahead of Open(). - base::StatisticsRecorder::Initialize(); - - SQLTestBase::SetUp(); - } - - // Handle errors by blowing away the database. - void RazeErrorCallback(int expected_error, int error, sql::Statement* stmt) { - // Nothing here needs extended errors at this time. - EXPECT_EQ(expected_error, expected_error&0xff); - EXPECT_EQ(expected_error, error&0xff); - db().RazeAndClose(); - } -}; +using SQLConnectionTest = sql::SQLTestBase; TEST_F(SQLConnectionTest, Execute) { // Valid statement should return true. @@ -318,12 +315,23 @@ TEST_F(SQLConnectionTest, IsSQLValidTest) { } TEST_F(SQLConnectionTest, DoesStuffExist) { - // Test DoesTableExist. + // Test DoesTableExist and DoesIndexExist. EXPECT_FALSE(db().DoesTableExist("foo")); ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); ASSERT_TRUE(db().Execute("CREATE INDEX foo_a ON foo (a)")); + EXPECT_FALSE(db().DoesIndexExist("foo")); EXPECT_TRUE(db().DoesTableExist("foo")); EXPECT_TRUE(db().DoesIndexExist("foo_a")); + EXPECT_FALSE(db().DoesTableExist("foo_a")); + + // Test DoesViewExist. The CREATE VIEW is an older form because some iOS + // versions use an earlier version of SQLite, and the difference isn't + // relevant for this test. + EXPECT_FALSE(db().DoesViewExist("voo")); + ASSERT_TRUE(db().Execute("CREATE VIEW voo AS SELECT 1")); + EXPECT_FALSE(db().DoesIndexExist("voo")); + EXPECT_FALSE(db().DoesTableExist("voo")); + EXPECT_TRUE(db().DoesViewExist("voo")); // Test DoesColumnExist. EXPECT_FALSE(db().DoesColumnExist("foo", "bar")); @@ -509,34 +517,76 @@ TEST_F(SQLConnectionTest, Raze) { } } -// Test that Raze() maintains page_size. +// Helper for SQLConnectionTest.RazePageSize. Creates a fresh db based on +// db_prefix, with the given initial page size, and verifies it against the +// expected size. Then changes to the final page size and razes, verifying that +// the fresh database ends up with the expected final page size. +void TestPageSize(const base::FilePath& db_prefix, + int initial_page_size, + const std::string& expected_initial_page_size, + int final_page_size, + const std::string& expected_final_page_size) { + const char kCreateSql[] = "CREATE TABLE x (t TEXT)"; + const char kInsertSql1[] = "INSERT INTO x VALUES ('This is a test')"; + const char kInsertSql2[] = "INSERT INTO x VALUES ('That was a test')"; + + const base::FilePath db_path = db_prefix.InsertBeforeExtensionASCII( + base::IntToString(initial_page_size)); + sql::Connection::Delete(db_path); + sql::Connection db; + db.set_page_size(initial_page_size); + ASSERT_TRUE(db.Open(db_path)); + ASSERT_TRUE(db.Execute(kCreateSql)); + ASSERT_TRUE(db.Execute(kInsertSql1)); + ASSERT_TRUE(db.Execute(kInsertSql2)); + ASSERT_EQ(expected_initial_page_size, + ExecuteWithResult(&db, "PRAGMA page_size")); + + // Raze will use the page size set in the connection object, which may not + // match the file's page size. + db.set_page_size(final_page_size); + ASSERT_TRUE(db.Raze()); + + // SQLite 3.10.2 (at least) has a quirk with the sqlite3_backup() API (used by + // Raze()) which causes the destination database to remember the previous + // page_size, even if the overwriting database changed the page_size. Access + // the actual database to cause the cached value to be updated. + EXPECT_EQ("0", ExecuteWithResult(&db, "SELECT COUNT(*) FROM sqlite_master")); + + EXPECT_EQ(expected_final_page_size, + ExecuteWithResult(&db, "PRAGMA page_size")); + EXPECT_EQ("1", ExecuteWithResult(&db, "PRAGMA page_count")); +} + +// Verify that sql::Recovery maintains the page size, and the virtual table +// works with page sizes other than SQLite's default. Also verify the case +// where the default page size has changed. TEST_F(SQLConnectionTest, RazePageSize) { - // Fetch the default page size and double it for use in this test. - // Scoped to release statement before Close(). - int default_page_size = 0; - { - sql::Statement s(db().GetUniqueStatement("PRAGMA page_size")); - ASSERT_TRUE(s.Step()); - default_page_size = s.ColumnInt(0); - } - ASSERT_GT(default_page_size, 0); - const int kPageSize = 2 * default_page_size; + const std::string default_page_size = + ExecuteWithResult(&db(), "PRAGMA page_size"); - // Re-open the database to allow setting the page size. - db().Close(); - db().set_page_size(kPageSize); - ASSERT_TRUE(db().Open(db_path())); + // The database should have the default page size after raze. + EXPECT_NO_FATAL_FAILURE( + TestPageSize(db_path(), 0, default_page_size, 0, default_page_size)); - // page_size should match the indicated value. - sql::Statement s(db().GetUniqueStatement("PRAGMA page_size")); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(kPageSize, s.ColumnInt(0)); + // Sync user 32k pages. + EXPECT_NO_FATAL_FAILURE( + TestPageSize(db_path(), 32768, "32768", 32768, "32768")); - // After raze, page_size should still match the indicated value. - ASSERT_TRUE(db().Raze()); - s.Reset(true); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(kPageSize, s.ColumnInt(0)); + // Many clients use 4k pages. This is the SQLite default after 3.12.0. + EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 4096, "4096", 4096, "4096")); + + // 1k is the default page size before 3.12.0. + EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 1024, "1024", 1024, "1024")); + + EXPECT_NO_FATAL_FAILURE( + TestPageSize(db_path(), 2048, "2048", 4096, "4096")); + + // Databases with no page size specified should result in the new default + // page size. 2k has never been the default page size. + ASSERT_NE("2048", default_page_size); + EXPECT_NO_FATAL_FAILURE( + TestPageSize(db_path(), 2048, "2048", 0, default_page_size)); } // Test that Raze() results are seen in other connections. @@ -692,8 +742,8 @@ TEST_F(SQLConnectionTest, RazeCallbackReopen) { ASSERT_TRUE(expecter.SawExpectedErrors()); } - db().set_error_callback(base::Bind(&SQLConnectionTest::RazeErrorCallback, - base::Unretained(this), + db().set_error_callback(base::Bind(&RazeErrorCallback, + &db(), SQLITE_CORRUPT)); // When the PRAGMA calls in Open() raise SQLITE_CORRUPT, the error @@ -938,8 +988,8 @@ TEST_F(SQLConnectionTest, Poison) { // Test that poisoning the database during a transaction works (with errors). // RazeErrorCallback() poisons the database, the extra COMMIT causes // CommitTransaction() to throw an error while commiting. - db().set_error_callback(base::Bind(&SQLConnectionTest::RazeErrorCallback, - base::Unretained(this), + db().set_error_callback(base::Bind(&RazeErrorCallback, + &db(), SQLITE_ERROR)); db().Close(); ASSERT_TRUE(db().Open(db_path())); @@ -1432,11 +1482,43 @@ TEST_F(SQLConnectionTest, MmapInitiallyEnabled) { sql::Connection::Delete(db_path()); db().set_mmap_disabled(); ASSERT_TRUE(db().Open(db_path())); + EXPECT_EQ("0", ExecuteWithResult(&db(), "PRAGMA mmap_size")); +} + +// Test whether a fresh database gets mmap enabled when using alternate status +// storage. +TEST_F(SQLConnectionTest, MmapInitiallyEnabledAltStatus) { + // Re-open fresh database with alt-status flag set. + db().Close(); + sql::Connection::Delete(db_path()); + db().set_mmap_alt_status(); + ASSERT_TRUE(db().Open(db_path())); + { sql::Statement s(db().GetUniqueStatement("PRAGMA mmap_size")); - ASSERT_TRUE(s.Step()); - EXPECT_LE(s.ColumnInt(0), 0); + + // SQLite doesn't have mmap support (perhaps an early iOS release). + if (!s.Step()) + return; + + // If mmap I/O is not on, attempt to turn it on. If that succeeds, then + // Open() should have turned it on. If mmap support is disabled, 0 is + // returned. If the VFS does not understand SQLITE_FCNTL_MMAP_SIZE (for + // instance MojoVFS), -1 is returned. + if (s.ColumnInt(0) <= 0) { + ASSERT_TRUE(db().Execute("PRAGMA mmap_size = 1048576")); + s.Reset(true); + ASSERT_TRUE(s.Step()); + EXPECT_LE(s.ColumnInt(0), 0); + } } + + // Test that explicit disable overrides set_mmap_alt_status(). + db().Close(); + sql::Connection::Delete(db_path()); + db().set_mmap_disabled(); + ASSERT_TRUE(db().Open(db_path())); + EXPECT_EQ("0", ExecuteWithResult(&db(), "PRAGMA mmap_size")); } TEST_F(SQLConnectionTest, GetAppropriateMmapSize) { @@ -1484,6 +1566,44 @@ TEST_F(SQLConnectionTest, GetAppropriateMmapSize) { ASSERT_EQ(MetaTable::kMmapSuccess, mmap_status); } +TEST_F(SQLConnectionTest, GetAppropriateMmapSizeAltStatus) { +#if defined(OS_IOS) && defined(USE_SYSTEM_SQLITE) + // Mmap is not supported on iOS9. Make sure that test takes precedence. + if (!base::ios::IsRunningOnIOS10OrLater()) { + db().set_mmap_alt_status(); + ASSERT_EQ(0UL, db().GetAppropriateMmapSize()); + return; + } +#endif + + const size_t kMmapAlot = 25 * 1024 * 1024; + + // At this point, Connection still expects a future [meta] table. + ASSERT_FALSE(db().DoesTableExist("meta")); + ASSERT_FALSE(db().DoesViewExist("MmapStatus")); + ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot); + ASSERT_FALSE(db().DoesTableExist("meta")); + ASSERT_FALSE(db().DoesViewExist("MmapStatus")); + + // Using alt status, everything should be mapped, with state in the view. + db().set_mmap_alt_status(); + ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot); + ASSERT_FALSE(db().DoesTableExist("meta")); + ASSERT_TRUE(db().DoesViewExist("MmapStatus")); + EXPECT_EQ(base::IntToString(MetaTable::kMmapSuccess), + ExecuteWithResult(&db(), "SELECT * FROM MmapStatus")); + + // Also maps everything when kMmapSuccess is in the view. + ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot); + + // Failure status leads to nothing being mapped. + ASSERT_TRUE(db().Execute("DROP VIEW MmapStatus")); + ASSERT_TRUE(db().Execute("CREATE VIEW MmapStatus AS SELECT -2")); + ASSERT_EQ(0UL, db().GetAppropriateMmapSize()); + EXPECT_EQ(base::IntToString(MetaTable::kMmapFailure), + ExecuteWithResult(&db(), "SELECT * FROM MmapStatus")); +} + // To prevent invalid SQL from accidentally shipping to production, prepared // statements which fail to compile with SQLITE_ERROR call DLOG(FATAL). This // case cannot be suppressed with an error callback. diff --git a/chromium/sql/meta_table.cc b/chromium/sql/meta_table.cc index b3e0503ad81..735cee327b0 100644 --- a/chromium/sql/meta_table.cc +++ b/chromium/sql/meta_table.cc @@ -7,7 +7,7 @@ #include <stdint.h> #include "base/logging.h" -#include "base/metrics/histogram.h" +#include "base/metrics/histogram_macros.h" #include "base/strings/string_util.h" #include "sql/connection.h" #include "sql/statement.h" diff --git a/chromium/sql/recovery_unittest.cc b/chromium/sql/recovery_unittest.cc index d9f589042b2..80e03544d1f 100644 --- a/chromium/sql/recovery_unittest.cc +++ b/chromium/sql/recovery_unittest.cc @@ -29,34 +29,8 @@ namespace { -// Execute |sql|, and stringify the results with |column_sep| between -// columns and |row_sep| between rows. -// TODO(shess): Promote this to a central testing helper. -std::string ExecuteWithResults(sql::Connection* db, - const char* sql, - const char* column_sep, - const char* row_sep) { - sql::Statement s(db->GetUniqueStatement(sql)); - std::string ret; - while (s.Step()) { - if (!ret.empty()) - ret += row_sep; - for (int i = 0; i < s.ColumnCount(); ++i) { - if (i > 0) - ret += column_sep; - 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; -} +using sql::test::ExecuteWithResults; +using sql::test::ExecuteWithResult; // Dump consistent human-readable representation of the database // schema. For tables or indices, this will contain the sql command @@ -153,8 +127,7 @@ TEST_F(SQLRecoveryTest, RecoverBasic) { ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); const char* kXSql = "SELECT * FROM x ORDER BY 1"; - ASSERT_EQ("That was a test", - ExecuteWithResults(&db(), kXSql, "|", "\n")); + ASSERT_EQ("That was a test", ExecuteWithResult(&db(), kXSql)); // Reset the database contents. ASSERT_TRUE(db().Execute("DELETE FROM x")); @@ -176,8 +149,7 @@ TEST_F(SQLRecoveryTest, RecoverBasic) { EXPECT_TRUE(db().is_open()); ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); - ASSERT_EQ("This is a test", - ExecuteWithResults(&db(), kXSql, "|", "\n")); + ASSERT_EQ("This is a test", ExecuteWithResult(&db(), kXSql)); } // Test operation of the virtual table used by sql::Recovery. @@ -340,12 +312,12 @@ TEST_F(SQLRecoveryTest, RecoverCorruptTable) { // Index shows one less than originally inserted. const char kCountSql[] = "SELECT COUNT (*) FROM x"; - EXPECT_EQ("9", ExecuteWithResults(&db(), kCountSql, "|", ",")); + EXPECT_EQ("9", ExecuteWithResult(&db(), kCountSql)); // A full table scan shows all of the original data. Using column [v] to // force use of the table rather than the index. const char kDistinctSql[] = "SELECT DISTINCT COUNT (v) FROM x"; - EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); + EXPECT_EQ("10", ExecuteWithResult(&db(), kDistinctSql)); // Insert id 0 again. Since it is not in the index, the insert // succeeds, but results in a duplicate value in the table. @@ -353,8 +325,8 @@ TEST_F(SQLRecoveryTest, RecoverCorruptTable) { ASSERT_TRUE(db().Execute(kInsertSql)); // Duplication is visible. - EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); - EXPECT_EQ("11", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); + EXPECT_EQ("10", ExecuteWithResult(&db(), kCountSql)); + EXPECT_EQ("11", ExecuteWithResult(&db(), kDistinctSql)); // This works before the callback is called. const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master"; @@ -374,12 +346,12 @@ TEST_F(SQLRecoveryTest, RecoverCorruptTable) { ASSERT_TRUE(Reopen()); // The recovered table has consistency between the index and the table. - EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); - EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); + EXPECT_EQ("10", ExecuteWithResult(&db(), kCountSql)); + EXPECT_EQ("10", ExecuteWithResult(&db(), kDistinctSql)); // Only one of the values is retained. const char kSelectSql[] = "SELECT v FROM x WHERE id = 0"; - const std::string results = ExecuteWithResults(&db(), kSelectSql, "|", ","); + const std::string results = ExecuteWithResult(&db(), kSelectSql); EXPECT_TRUE(results=="100" || results=="0") << "Actual results: " << results; } @@ -867,4 +839,75 @@ TEST_F(SQLRecoveryTest, AttachFailure) { tester.ExpectBucketCount(kErrorHistogramName, SQLITE_NOTADB, 1); } +// Helper for SQLRecoveryTest.PageSize. Creates a fresh db based on db_prefix, +// with the given initial page size, and verifies it against the expected size. +// Then changes to the final page size and recovers, verifying that the +// recovered database ends up with the expected final page size. +void TestPageSize(const base::FilePath& db_prefix, + int initial_page_size, + const std::string& expected_initial_page_size, + int final_page_size, + const std::string& expected_final_page_size) { + const char kCreateSql[] = "CREATE TABLE x (t TEXT)"; + const char kInsertSql1[] = "INSERT INTO x VALUES ('This is a test')"; + const char kInsertSql2[] = "INSERT INTO x VALUES ('That was a test')"; + const char kSelectSql[] = "SELECT * FROM x ORDER BY t"; + + const base::FilePath db_path = db_prefix.InsertBeforeExtensionASCII( + base::IntToString(initial_page_size)); + sql::Connection::Delete(db_path); + sql::Connection db; + db.set_page_size(initial_page_size); + ASSERT_TRUE(db.Open(db_path)); + ASSERT_TRUE(db.Execute(kCreateSql)); + ASSERT_TRUE(db.Execute(kInsertSql1)); + ASSERT_TRUE(db.Execute(kInsertSql2)); + ASSERT_EQ(expected_initial_page_size, + ExecuteWithResult(&db, "PRAGMA page_size")); + + // Recovery will use the page size set in the connection object, which may not + // match the file's page size. + db.set_page_size(final_page_size); + sql::Recovery::RecoverDatabase(&db, db_path); + + // Recovery poisoned the handle, must re-open. + db.Close(); + + // Make sure the page size is read from the file. + db.set_page_size(0); + ASSERT_TRUE(db.Open(db_path)); + ASSERT_EQ(expected_final_page_size, + ExecuteWithResult(&db, "PRAGMA page_size")); + EXPECT_EQ("That was a test\nThis is a test", + ExecuteWithResults(&db, kSelectSql, "|", "\n")); +} + +// Verify that sql::Recovery maintains the page size, and the virtual table +// works with page sizes other than SQLite's default. Also verify the case +// where the default page size has changed. +TEST_F(SQLRecoveryTest, PageSize) { + const std::string default_page_size = + ExecuteWithResult(&db(), "PRAGMA page_size"); + + // The database should have the default page size after recovery. + EXPECT_NO_FATAL_FAILURE( + TestPageSize(db_path(), 0, default_page_size, 0, default_page_size)); + + // Sync user 32k pages. + EXPECT_NO_FATAL_FAILURE( + TestPageSize(db_path(), 32768, "32768", 32768, "32768")); + + // Many clients use 4k pages. This is the SQLite default after 3.12.0. + EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 4096, "4096", 4096, "4096")); + + // 1k is the default page size before 3.12.0. + EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 1024, "1024", 1024, "1024")); + + // Databases with no page size specified should recover with the new default + // page size. 2k has never been the default page size. + ASSERT_NE("2048", default_page_size); + EXPECT_NO_FATAL_FAILURE( + TestPageSize(db_path(), 2048, "2048", 0, default_page_size)); +} + } // namespace diff --git a/chromium/sql/sqlite_features_unittest.cc b/chromium/sql/sqlite_features_unittest.cc index b84a80d69c0..199d9446c73 100644 --- a/chromium/sql/sqlite_features_unittest.cc +++ b/chromium/sql/sqlite_features_unittest.cc @@ -270,4 +270,37 @@ TEST_F(SQLiteFeaturesTest, Mmap) { } #endif +// Verify that http://crbug.com/248608 is fixed. In this bug, the +// compiled regular expression is effectively cached with the prepared +// statement, causing errors if the regular expression is rebound. +TEST_F(SQLiteFeaturesTest, CachedRegexp) { + ASSERT_TRUE(db().Execute("CREATE TABLE r (id INTEGER UNIQUE, x TEXT)")); + ASSERT_TRUE(db().Execute("INSERT INTO r VALUES (1, 'this is a test')")); + ASSERT_TRUE(db().Execute("INSERT INTO r VALUES (2, 'that was a test')")); + ASSERT_TRUE(db().Execute("INSERT INTO r VALUES (3, 'this is a stickup')")); + ASSERT_TRUE(db().Execute("INSERT INTO r VALUES (4, 'that sucks')")); + + const char* kSimpleSql = "SELECT SUM(id) FROM r WHERE x REGEXP ?"; + sql::Statement s(db().GetCachedStatement(SQL_FROM_HERE, kSimpleSql)); + + s.BindString(0, "this.*"); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(4, s.ColumnInt(0)); + + s.Reset(true); + s.BindString(0, "that.*"); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(6, s.ColumnInt(0)); + + s.Reset(true); + s.BindString(0, ".*test"); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(3, s.ColumnInt(0)); + + s.Reset(true); + s.BindString(0, ".* s[a-z]+"); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(7, s.ColumnInt(0)); +} + } // namespace |