summaryrefslogtreecommitdiff
path: root/chromium/sql
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-03-08 10:28:10 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-03-20 13:40:30 +0000
commite733310db58160074f574c429d48f8308c0afe17 (patch)
treef8aef4b7e62a69928dbcf880620eece20f98c6df /chromium/sql
parent2f583e4aec1ae3a86fa047829c96b310dc12ecdf (diff)
downloadqtwebengine-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.cc109
-rw-r--r--chromium/sql/connection.h29
-rw-r--r--chromium/sql/connection_unittest.cc220
-rw-r--r--chromium/sql/meta_table.cc2
-rw-r--r--chromium/sql/recovery_unittest.cc121
-rw-r--r--chromium/sql/sqlite_features_unittest.cc33
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