Commit be4f97ce authored by Shubham Aggarwal's avatar Shubham Aggarwal Committed by Commit Bot

SQLite: Add support for WAL mode

As per issue 78507, we are looking to add support for SQLite databases
to use Write-ahead logging (https://www.sqlite.org/wal.html) mode in
Chromium. WAL mode should give us significant performance gains across
almost all use-cases.

This change is a first step towards achieving this. It adds opt-in
support to enable WAL mode for a database connection and perform a
checkpoint. It also adds a feature flag to enable WAL mode for all
databases by default to investigate its feasibility and impact on
performance.

Bug: 78507
Change-Id: I7fc5edcc39b50d2a13755d587cf342bded1af60a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2095927
Commit-Queue: Shubham Aggarwal <shuagga@microsoft.com>
Reviewed-by: default avatarBrandon Maslen <brandm@microsoft.com>
Reviewed-by: default avatarVictor Costan <pwnall@chromium.org>
Reviewed-by: default avatarChris Mumford <cmumford@google.com>
Cr-Commit-Position: refs/heads/master@{#780318}
parent 9fcf48bb
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include "base/feature_list.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/format_macros.h" #include "base/format_macros.h"
...@@ -241,6 +242,8 @@ Database::Database() ...@@ -241,6 +242,8 @@ Database::Database()
page_size_(kDefaultPageSize), page_size_(kDefaultPageSize),
cache_size_(0), cache_size_(0),
exclusive_locking_(false), exclusive_locking_(false),
want_wal_mode_(
base::FeatureList::IsEnabled(features::kEnableWALModeByDefault)),
transaction_nesting_(0), transaction_nesting_(0),
needs_rollback_(false), needs_rollback_(false),
in_memory_(false), in_memory_(false),
...@@ -813,8 +816,9 @@ bool Database::Raze() { ...@@ -813,8 +816,9 @@ bool Database::Raze() {
return false; return false;
} }
const std::string sql = base::StringPrintf("PRAGMA page_size=%d", page_size_); const std::string page_size_sql =
if (!null_db.Execute(sql.c_str())) base::StringPrintf("PRAGMA page_size=%d", page_size_);
if (!null_db.Execute(page_size_sql.c_str()))
return false; return false;
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
...@@ -894,9 +898,39 @@ bool Database::Raze() { ...@@ -894,9 +898,39 @@ bool Database::Raze() {
DCHECK_EQ(rc, SQLITE_DONE) << "Failed retrying Raze()."; DCHECK_EQ(rc, SQLITE_DONE) << "Failed retrying Raze().";
} }
// Page size of |db_| and |null_db| differ.
if (rc == SQLITE_READONLY) {
// Enter TRUNCATE mode to change page size.
// TODO(shuagga@microsoft.com): Need a guarantee here that there is no other
// database connection open.
ignore_result(Execute("PRAGMA journal_mode=TRUNCATE;"));
if (!Execute(page_size_sql.c_str())) {
return false;
}
// Page size isn't changed until the database is vacuumed.
ignore_result(Execute("VACUUM"));
// Re-enter WAL mode.
if (UseWALMode()) {
ignore_result(Execute("PRAGMA journal_mode=WAL;"));
}
rc = BackupDatabase(null_db.db_, db_, kMain);
base::UmaHistogramSparse("Sqlite.RazeDatabase2", rc);
DCHECK_EQ(rc, SQLITE_DONE) << "Failed retrying Raze().";
}
// TODO(shess): Figure out which other cases can happen. // TODO(shess): Figure out which other cases can happen.
DCHECK_EQ(rc, SQLITE_DONE) << "Unable to copy entire null database."; DCHECK_EQ(rc, SQLITE_DONE) << "Unable to copy entire null database.";
// Checkpoint to propagate transactions to the database file and empty the WAL
// file.
// The database can still contain old data if the Checkpoint fails so fail the
// Raze.
if (!CheckpointDatabase()) {
return false;
}
// The entire database should have been backed up. // The entire database should have been backed up.
return rc == SQLITE_DONE; return rc == SQLITE_DONE;
} }
...@@ -1449,6 +1483,24 @@ bool Database::OpenInternal(const std::string& file_name, ...@@ -1449,6 +1483,24 @@ bool Database::OpenInternal(const std::string& file_name,
return false; return false;
} }
// If indicated, lock up the database before doing anything else, so
// that the following code doesn't have to deal with locking.
//
// Needs to happen before any other operation is performed in WAL mode so that
// no operation relies on shared memory if exclusive locking is turned on.
//
// TODO(shess): This code is brittle. Find the cases where code
// doesn't request |exclusive_locking_| and audit that it does the
// right thing with SQLITE_BUSY, and that it doesn't make
// assumptions about who might change things in the database.
// http://crbug.com/56559
if (exclusive_locking_) {
// TODO(shess): This should probably be a failure. Code which
// requests exclusive locking but doesn't get it is almost certain
// to be ill-tested.
ignore_result(Execute("PRAGMA locking_mode=EXCLUSIVE"));
}
// Enable extended result codes to provide more color on I/O errors. // Enable extended result codes to provide more color on I/O errors.
// Not having extended result codes is not a fatal problem, as // Not having extended result codes is not a fatal problem, as
// Chromium code does not attempt to handle I/O errors anyhow. The // Chromium code does not attempt to handle I/O errors anyhow. The
...@@ -1480,35 +1532,43 @@ bool Database::OpenInternal(const std::string& file_name, ...@@ -1480,35 +1532,43 @@ bool Database::OpenInternal(const std::string& file_name,
} }
} }
// If indicated, lock up the database before doing anything else, so const base::TimeDelta kBusyTimeout =
// that the following code doesn't have to deal with locking. base::TimeDelta::FromSeconds(kBusyTimeoutSeconds);
// TODO(shess): This code is brittle. Find the cases where code
// doesn't request |exclusive_locking_| and audit that it does the // Needs to happen before entering WAL mode. Will only work if this the first
// right thing with SQLITE_BUSY, and that it doesn't make // time the database is being opened in WAL mode.
// assumptions about who might change things in the database. const std::string page_size_sql =
// http://crbug.com/56559 base::StringPrintf("PRAGMA page_size=%d", page_size_);
if (exclusive_locking_) { ignore_result(ExecuteWithTimeout(page_size_sql.c_str(), kBusyTimeout));
// TODO(shess): This should probably be a failure. Code which
// requests exclusive locking but doesn't get it is almost certain
// to be ill-tested.
ignore_result(Execute("PRAGMA locking_mode=EXCLUSIVE"));
}
// http://www.sqlite.org/pragma.html#pragma_journal_mode // http://www.sqlite.org/pragma.html#pragma_journal_mode
// WAL - Use a write-ahead log instead of a journal file.
// DELETE (default) - delete -journal file to commit. // DELETE (default) - delete -journal file to commit.
// TRUNCATE - truncate -journal file to commit. // TRUNCATE - truncate -journal file to commit.
// PERSIST - zero out header of -journal file to commit. // PERSIST - zero out header of -journal file to commit.
// TRUNCATE should be faster than DELETE because it won't need directory // TRUNCATE should be faster than DELETE because it won't need directory
// changes for each transaction. PERSIST may break the spirit of using // changes for each transaction. PERSIST may break the spirit of using
// secure_delete. // secure_delete.
ignore_result(Execute("PRAGMA journal_mode=TRUNCATE")); //
// Needs to be performed after setting exclusive locking mode. Otherwise can
const base::TimeDelta kBusyTimeout = // fail if underlying VFS doesn't support shared memory.
base::TimeDelta::FromSeconds(kBusyTimeoutSeconds); if (UseWALMode()) {
// Set the synchronous flag to NORMAL. This means that writers don't flush
const std::string page_size_sql = // the WAL file after every write. The WAL file is only flushed on a
base::StringPrintf("PRAGMA page_size=%d", page_size_); // checkpoint. In this case, transcations might lose durability on a power
ignore_result(ExecuteWithTimeout(page_size_sql.c_str(), kBusyTimeout)); // loss (but still durable after an application crash).
// TODO(shuagga@microsoft.com): Evaluate if this loss of durability is a
// concern.
ignore_result(Execute("PRAGMA synchronous=NORMAL"));
// Opening the db in WAL mode can fail (eg if the underlying VFS doesn't
// support shared memory and we are not in exclusive locking mode).
//
// TODO(shuagga@microsoft.com): We should probably catch a failure here.
ignore_result(Execute("PRAGMA journal_mode=WAL"));
} else {
ignore_result(Execute("PRAGMA journal_mode=TRUNCATE"));
}
if (cache_size_ != 0) { if (cache_size_ != 0) {
const std::string cache_size_sql = const std::string cache_size_sql =
...@@ -1732,4 +1792,28 @@ bool Database::ReportMemoryUsage(base::trace_event::ProcessMemoryDump* pmd, ...@@ -1732,4 +1792,28 @@ bool Database::ReportMemoryUsage(base::trace_event::ProcessMemoryDump* pmd,
memory_dump_provider_->ReportMemoryUsage(pmd, dump_name); memory_dump_provider_->ReportMemoryUsage(pmd, dump_name);
} }
bool Database::UseWALMode() const {
#if defined(OS_FUCHSIA)
// WAL mode is only enabled on Fuchsia for databases with exclusive
// locking, because this case does not require shared memory support.
// At the time this was implemented (May 2020), Fuchsia's shared
// memory support was insufficient for SQLite's needs.
return want_wal_mode_ && exclusive_locking_;
#else
return want_wal_mode_;
#endif // defined(OS_FUCHSIA)
}
bool Database::CheckpointDatabase() {
base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
InitScopedBlockingCall(FROM_HERE, &scoped_blocking_call);
static const char* kMainDb = "main";
int rc = sqlite3_wal_checkpoint_v2(db_, kMainDb, SQLITE_CHECKPOINT_PASSIVE,
/*pnLog=*/nullptr,
/*pnCkpt=*/nullptr);
return rc == SQLITE_OK;
}
} // namespace sql } // namespace sql
...@@ -88,6 +88,23 @@ class COMPONENT_EXPORT(SQL) Database { ...@@ -88,6 +88,23 @@ class COMPONENT_EXPORT(SQL) Database {
cache_size_ = cache_size; cache_size_ = cache_size;
} }
// Returns whether a database will be opened in WAL mode.
bool UseWALMode() const;
// Enables/disables WAL mode (https://www.sqlite.org/wal.html) when
// opening a new database.
//
// WAL mode is currently not fully supported on FuchsiaOS. It will only be
// turned on if the database is also using exclusive locking mode.
// (https://crbug.com/1082059)
//
// Note: Changing page size is not supported when in WAL mode. So running
// 'PRAGMA page_size = <new-size>' or using set_page_size will result in
// no-ops.
//
// This must be called before Open() to have an effect.
void want_wal_mode(bool enabled) { want_wal_mode_ = enabled; }
// Call to put the database in exclusive locking mode. There is no "back to // Call to put the database in exclusive locking mode. There is no "back to
// normal" flag because of some additional requirements sqlite puts on this // normal" flag because of some additional requirements sqlite puts on this
// transaction (requires another access to the DB) and because we don't // transaction (requires another access to the DB) and because we don't
...@@ -396,6 +413,14 @@ class COMPONENT_EXPORT(SQL) Database { ...@@ -396,6 +413,14 @@ class COMPONENT_EXPORT(SQL) Database {
// See GetCachedStatement above for examples and error information. // See GetCachedStatement above for examples and error information.
scoped_refptr<StatementRef> GetUniqueStatement(const char* sql); scoped_refptr<StatementRef> GetUniqueStatement(const char* sql);
// Performs a passive checkpoint on the main attached database if it is in
// WAL mode. Returns true if the checkpoint was successful and false in case
// of an error. It is a no-op if the database is not in WAL mode.
//
// Note: Checkpointing is a very slow operation and will block any writes
// until it is finished. Please use with care.
bool CheckpointDatabase();
// Info querying ------------------------------------------------------------- // Info querying -------------------------------------------------------------
// Returns true if the given structure exists. Instead of test-then-create, // Returns true if the given structure exists. Instead of test-then-create,
...@@ -693,7 +718,9 @@ class COMPONENT_EXPORT(SQL) Database { ...@@ -693,7 +718,9 @@ class COMPONENT_EXPORT(SQL) Database {
// use the default value. // use the default value.
int page_size_; int page_size_;
int cache_size_; int cache_size_;
bool exclusive_locking_; bool exclusive_locking_;
bool want_wal_mode_;
// Holds references to all cached statements so they remain active. // Holds references to all cached statements so they remain active.
// //
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "sql/database.h" #include "sql/database.h"
#include "sql/database_memory_dump_provider.h" #include "sql/database_memory_dump_provider.h"
#include "sql/meta_table.h" #include "sql/meta_table.h"
#include "sql/sql_features.h"
#include "sql/statement.h" #include "sql/statement.h"
#include "sql/test/database_test_peer.h" #include "sql/test/database_test_peer.h"
#include "sql/test/error_callback_support.h" #include "sql/test/error_callback_support.h"
...@@ -538,14 +539,18 @@ TEST_F(SQLDatabaseTest, RazeLocked) { ...@@ -538,14 +539,18 @@ TEST_F(SQLDatabaseTest, RazeLocked) {
// An unfinished read transaction in the other connection also // An unfinished read transaction in the other connection also
// blocks raze. // blocks raze.
const char* kQuery = "SELECT COUNT(*) FROM foo"; // This doesn't happen in WAL mode because reads are no longer blocked by
sql::Statement s(other_db.GetUniqueStatement(kQuery)); // write operations when using a WAL.
ASSERT_TRUE(s.Step()); if (!base::FeatureList::IsEnabled(sql::features::kEnableWALModeByDefault)) {
ASSERT_FALSE(db().Raze()); const char* kQuery = "SELECT COUNT(*) FROM foo";
sql::Statement s(other_db.GetUniqueStatement(kQuery));
ASSERT_TRUE(s.Step());
ASSERT_FALSE(db().Raze());
// Complete the statement unlocks the database. // Completing the statement unlocks the database.
ASSERT_FALSE(s.Step()); ASSERT_FALSE(s.Step());
ASSERT_TRUE(db().Raze()); ASSERT_TRUE(db().Raze());
}
} }
// Verify that Raze() can handle an empty file. SQLite should treat // Verify that Raze() can handle an empty file. SQLite should treat
...@@ -769,6 +774,11 @@ TEST_F(SQLDatabaseTest, RazeTruncate) { ...@@ -769,6 +774,11 @@ TEST_F(SQLDatabaseTest, RazeTruncate) {
ASSERT_TRUE( ASSERT_TRUE(
db().Execute("INSERT INTO foo (value) VALUES (randomblob(1024))")); db().Execute("INSERT INTO foo (value) VALUES (randomblob(1024))"));
} }
// In WAL mode, writes don't reach the database file until a checkpoint
// happens.
ASSERT_TRUE(db().CheckpointDatabase());
int64_t db_size; int64_t db_size;
ASSERT_TRUE(base::GetFileSize(db_path(), &db_size)); ASSERT_TRUE(base::GetFileSize(db_path(), &db_size));
ASSERT_GT(db_size, expected_size); ASSERT_GT(db_size, expected_size);
...@@ -796,23 +806,55 @@ TEST_F(SQLDatabaseTest, SetTempDirForSQL) { ...@@ -796,23 +806,55 @@ TEST_F(SQLDatabaseTest, SetTempDirForSQL) {
} }
#endif // defined(OS_ANDROID) #endif // defined(OS_ANDROID)
TEST_F(SQLDatabaseTest, DeleteNonWal) { // Contained param indicates whether WAL mode is switched on or not.
class JournalModeTest : public SQLDatabaseTest,
public testing::WithParamInterface<bool> {
public:
void SetUp() override {
#if defined(OS_FUCHSIA) // Exclusive mode needs to be enabled to enter WAL mode
// on Fuchsia
if (IsWALEnabled()) {
db().set_exclusive_locking();
}
#endif // defined(OS_FUCHSIA)
db().want_wal_mode(IsWALEnabled());
SQLDatabaseTest::SetUp();
}
bool IsWALEnabled() { return GetParam(); }
};
TEST_P(JournalModeTest, Delete) {
EXPECT_TRUE(db().Execute("CREATE TABLE x (x)")); EXPECT_TRUE(db().Execute("CREATE TABLE x (x)"));
db().Close(); db().Close();
// Should have both a main database file and a journal file because
// of journal_mode TRUNCATE.
base::FilePath journal_path = sql::Database::JournalPath(db_path()); base::FilePath journal_path = sql::Database::JournalPath(db_path());
base::FilePath wal_path = sql::Database::WriteAheadLogPath(db_path());
// Should have both a main database file and a journal file if
// journal_mode is TRUNCATE. There is no WAL file as it is deleted on Close.
ASSERT_TRUE(GetPathExists(db_path())); ASSERT_TRUE(GetPathExists(db_path()));
ASSERT_TRUE(GetPathExists(journal_path)); if (!IsWALEnabled()) { // TRUNCATE mode
ASSERT_TRUE(GetPathExists(journal_path));
}
sql::Database::Delete(db_path()); sql::Database::Delete(db_path());
EXPECT_FALSE(GetPathExists(db_path())); EXPECT_FALSE(GetPathExists(db_path()));
EXPECT_FALSE(GetPathExists(journal_path)); EXPECT_FALSE(GetPathExists(journal_path));
EXPECT_FALSE(GetPathExists(wal_path));
} }
// WAL mode is currently not supported on Fuchsia
#if !defined(OS_FUCHSIA)
INSTANTIATE_TEST_SUITE_P(SQLDatabaseTest, JournalModeTest, testing::Bool());
#else
INSTANTIATE_TEST_SUITE_P(SQLDatabaseTest,
JournalModeTest,
testing::Values(false));
#endif
#if defined(OS_POSIX) // This test operates on POSIX file permissions. #if defined(OS_POSIX) // This test operates on POSIX file permissions.
TEST_F(SQLDatabaseTest, PosixFilePermissions) { TEST_P(JournalModeTest, PosixFilePermissions) {
db().Close(); db().Close();
sql::Database::Delete(db_path()); sql::Database::Delete(db_path());
ASSERT_FALSE(GetPathExists(db_path())); ASSERT_FALSE(GetPathExists(db_path()));
...@@ -834,26 +876,10 @@ TEST_F(SQLDatabaseTest, PosixFilePermissions) { ...@@ -834,26 +876,10 @@ TEST_F(SQLDatabaseTest, PosixFilePermissions) {
EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode)); EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode));
ASSERT_EQ(mode, 0600); ASSERT_EQ(mode, 0600);
{ if (IsWALEnabled()) { // WAL mode
base::FilePath journal_path = sql::Database::JournalPath(db_path()); // The WAL file is created lazily on first change.
DLOG(ERROR) << "journal_path: " << journal_path; ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
ASSERT_TRUE(GetPathExists(journal_path));
EXPECT_TRUE(base::GetPosixFilePermissions(journal_path, &mode));
ASSERT_EQ(mode, 0600);
}
// Reopen the database and turn on WAL mode.
db().Close();
sql::Database::Delete(db_path());
ASSERT_FALSE(GetPathExists(db_path()));
ASSERT_TRUE(db().Open(db_path()));
ASSERT_TRUE(db().Execute("PRAGMA journal_mode = WAL"));
ASSERT_EQ("wal", ExecuteWithResult(&db(), "PRAGMA journal_mode"));
// The WAL file is created lazily on first change.
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
{
base::FilePath wal_path = sql::Database::WriteAheadLogPath(db_path()); base::FilePath wal_path = sql::Database::WriteAheadLogPath(db_path());
ASSERT_TRUE(GetPathExists(wal_path)); ASSERT_TRUE(GetPathExists(wal_path));
EXPECT_TRUE(base::GetPosixFilePermissions(wal_path, &mode)); EXPECT_TRUE(base::GetPosixFilePermissions(wal_path, &mode));
...@@ -863,6 +889,12 @@ TEST_F(SQLDatabaseTest, PosixFilePermissions) { ...@@ -863,6 +889,12 @@ TEST_F(SQLDatabaseTest, PosixFilePermissions) {
ASSERT_TRUE(GetPathExists(shm_path)); ASSERT_TRUE(GetPathExists(shm_path));
EXPECT_TRUE(base::GetPosixFilePermissions(shm_path, &mode)); EXPECT_TRUE(base::GetPosixFilePermissions(shm_path, &mode));
ASSERT_EQ(mode, 0600); ASSERT_EQ(mode, 0600);
} else { // Truncate mode
base::FilePath journal_path = sql::Database::JournalPath(db_path());
DLOG(ERROR) << "journal_path: " << journal_path;
ASSERT_TRUE(GetPathExists(journal_path));
EXPECT_TRUE(base::GetPosixFilePermissions(journal_path, &mode));
ASSERT_EQ(mode, 0600);
} }
} }
#endif // defined(OS_POSIX) #endif // defined(OS_POSIX)
...@@ -1220,6 +1252,84 @@ TEST_F(SQLDatabaseTest, GetAppropriateMmapSizeAltStatus) { ...@@ -1220,6 +1252,84 @@ TEST_F(SQLDatabaseTest, GetAppropriateMmapSizeAltStatus) {
ExecuteWithResult(&db(), "SELECT * FROM MmapStatus")); ExecuteWithResult(&db(), "SELECT * FROM MmapStatus"));
} }
TEST_F(SQLDatabaseTest, EnableWALMode) {
db().want_wal_mode(true);
#if defined(OS_FUCHSIA) // Exclusive mode needs to be enabled to enter WAL mode
// on Fuchsia
db().set_exclusive_locking();
#endif // defined(OS_FUCHSIA)
ASSERT_TRUE(Reopen());
EXPECT_EQ(ExecuteWithResult(&db(), "PRAGMA journal_mode"), "wal");
}
TEST_F(SQLDatabaseTest, DisableWALMode) {
db().want_wal_mode(true);
#if defined(OS_FUCHSIA) // Exclusive mode needs to be enabled to enter WAL mode
// on Fuchsia
db().set_exclusive_locking();
#endif // defined(OS_FUCHSIA)
ASSERT_TRUE(Reopen());
ASSERT_EQ(ExecuteWithResult(&db(), "PRAGMA journal_mode"), "wal");
// Add some data to ensure that disabling WAL mode correctly handles a
// populated WAL file.
ASSERT_TRUE(
db().Execute("CREATE TABLE foo (id INTEGER UNIQUE, value INTEGER)"));
ASSERT_TRUE(db().Execute("INSERT INTO foo VALUES (1, 1)"));
ASSERT_TRUE(db().Execute("INSERT INTO foo VALUES (2, 2)"));
db().want_wal_mode(false);
ASSERT_TRUE(Reopen());
EXPECT_EQ(ExecuteWithResult(&db(), "PRAGMA journal_mode"), "truncate");
// Check that data is preserved
EXPECT_EQ(ExecuteWithResult(&db(), "SELECT SUM(value) FROM foo WHERE id < 3"),
"3");
}
TEST_F(SQLDatabaseTest, CheckpointDatabase) {
if (!db().UseWALMode()) {
db().Close();
sql::Database::Delete(db_path());
db().want_wal_mode(true);
#if defined(OS_FUCHSIA) // Exclusive mode needs to be enabled to enter WAL mode
// on Fuchsia
db().set_exclusive_locking();
#endif // defined(OS_FUCHSIA)
ASSERT_TRUE(db().Open(db_path()));
ASSERT_EQ(ExecuteWithResult(&db(), "PRAGMA journal_mode"), "wal");
}
base::FilePath wal_path = sql::Database::WriteAheadLogPath(db_path());
int64_t wal_size = 0;
// WAL file initially empty.
EXPECT_TRUE(GetPathExists(wal_path));
base::GetFileSize(wal_path, &wal_size);
EXPECT_EQ(wal_size, 0);
ASSERT_TRUE(
db().Execute("CREATE TABLE foo (id INTEGER UNIQUE, value INTEGER)"));
ASSERT_TRUE(db().Execute("INSERT INTO foo VALUES (1, 1)"));
ASSERT_TRUE(db().Execute("INSERT INTO foo VALUES (2, 2)"));
// Writes reach WAL file but not db file.
base::GetFileSize(wal_path, &wal_size);
EXPECT_GT(wal_size, 0);
int64_t db_size = 0;
base::GetFileSize(db_path(), &db_size);
EXPECT_EQ(db_size, db().page_size());
// Checkpoint database to immediately propagate writes to DB file.
EXPECT_TRUE(db().CheckpointDatabase());
base::GetFileSize(db_path(), &db_size);
EXPECT_GT(db_size, db().page_size());
EXPECT_EQ(ExecuteWithResult(&db(), "SELECT value FROM foo where id=1"), "1");
EXPECT_EQ(ExecuteWithResult(&db(), "SELECT value FROM foo where id=2"), "2");
}
// To prevent invalid SQL from accidentally shipping to production, prepared // To prevent invalid SQL from accidentally shipping to production, prepared
// statements which fail to compile with SQLITE_ERROR call DLOG(DCHECK). This // statements which fail to compile with SQLITE_ERROR call DLOG(DCHECK). This
// case cannot be suppressed with an error callback. // case cannot be suppressed with an error callback.
......
...@@ -20,6 +20,10 @@ namespace features { ...@@ -20,6 +20,10 @@ namespace features {
const base::Feature kSqlSkipPreload{"SqlSkipPreload", const base::Feature kSqlSkipPreload{"SqlSkipPreload",
base::FEATURE_DISABLED_BY_DEFAULT}; base::FEATURE_DISABLED_BY_DEFAULT};
// Enable WAL mode for all SQLite databases.
const base::Feature kEnableWALModeByDefault{"EnableWALModeByDefault",
base::FEATURE_DISABLED_BY_DEFAULT};
} // namespace features } // namespace features
} // namespace sql } // namespace sql
...@@ -14,6 +14,8 @@ namespace features { ...@@ -14,6 +14,8 @@ namespace features {
COMPONENT_EXPORT(SQL) extern const base::Feature kSqlSkipPreload; COMPONENT_EXPORT(SQL) extern const base::Feature kSqlSkipPreload;
COMPONENT_EXPORT(SQL) extern const base::Feature kEnableWALModeByDefault;
} // namespace features } // namespace features
} // namespace sql } // namespace sql
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment