blob: f90e9c9fe915ea63ca3254ba6cb61c3c55a4c08d [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_helpers.h"
#include "sql/database.h"
#include "sql/sqlite_result_code.h"
#include "sql/statement.h"
#include "sql/test/scoped_error_expecter.h"
#include "sql/test/test_helpers.h"
#include "sql/transaction.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/sqlite/sqlite3.h"
namespace sql {
namespace {
enum class OpenVariant {
kInMemory = 1,
kOnDiskExclusiveJournal = 2,
kOnDiskNonExclusiveJournal = 3,
kOnDiskExclusiveWal = 4,
};
// We use the parameter to run all tests with WAL mode on and off.
class DatabaseOptionsTest : public testing::TestWithParam<OpenVariant> {
public:
DatabaseOptionsTest() = default;
~DatabaseOptionsTest() override = default;
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
db_path_ = temp_dir_.GetPath().AppendASCII("database_test.sqlite");
}
OpenVariant open_variant() const { return GetParam(); }
// The options below interact with all other options. These tests ensure that
// all combinations work.
bool exclusive_locking() const {
return GetParam() != OpenVariant::kOnDiskNonExclusiveJournal;
}
bool wal_mode() const {
return GetParam() == OpenVariant::kOnDiskExclusiveWal;
}
void OpenDatabase(Database& db) {
switch (open_variant()) {
case OpenVariant::kOnDiskExclusiveJournal:
ASSERT_TRUE(db.Open(db_path_));
break;
case OpenVariant::kOnDiskNonExclusiveJournal:
ASSERT_TRUE(db.Open(db_path_));
break;
case OpenVariant::kOnDiskExclusiveWal:
ASSERT_TRUE(db.Open(db_path_));
break;
case OpenVariant::kInMemory:
ASSERT_TRUE(db.OpenInMemory());
break;
}
}
// Runs a rolled back transaction, followed by a committed transaction.
void RunTransactions(Database& db) {
{
Transaction rolled_back(&db);
ASSERT_TRUE(rolled_back.Begin());
ASSERT_TRUE(db.Execute("CREATE TABLE rows(id PRIMARY KEY NOT NULL)"));
rolled_back.Rollback();
}
{
Transaction committed(&db);
ASSERT_TRUE(committed.Begin());
ASSERT_TRUE(db.Execute("CREATE TABLE rows(id PRIMARY KEY NOT NULL)"));
ASSERT_TRUE(committed.Commit());
}
}
protected:
base::ScopedTempDir temp_dir_;
base::FilePath db_path_;
};
TEST_P(DatabaseOptionsTest, FlushToDisk_FalseByDefault) {
DatabaseOptions options = {
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
};
EXPECT_FALSE(options.flush_to_media) << "Invalid test assumption";
Database db(options);
OpenDatabase(db);
EXPECT_EQ("0", sql::test::ExecuteWithResult(&db, "PRAGMA fullfsync"));
}
TEST_P(DatabaseOptionsTest, FlushToDisk_True) {
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.flush_to_media = true,
});
OpenDatabase(db);
EXPECT_EQ("1", sql::test::ExecuteWithResult(&db, "PRAGMA fullfsync"));
}
TEST_P(DatabaseOptionsTest, FlushToDisk_False_DoesNotCrash) {
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.flush_to_media = false,
});
OpenDatabase(db);
EXPECT_EQ("0", sql::test::ExecuteWithResult(&db, "PRAGMA fullfsync"))
<< "Invalid test setup";
RunTransactions(db);
}
TEST_P(DatabaseOptionsTest, FlushToDisk_True_DoesNotCrash) {
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.flush_to_media = true,
});
OpenDatabase(db);
EXPECT_EQ("1", sql::test::ExecuteWithResult(&db, "PRAGMA fullfsync"))
<< "Invalid test setup";
RunTransactions(db);
}
TEST_P(DatabaseOptionsTest, PageSize_Default) {
static_assert(DatabaseOptions::kDefaultPageSize == 4096,
"The page size numbers in this test file need to change");
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.page_size = 4096,
});
OpenDatabase(db);
EXPECT_EQ("4096", sql::test::ExecuteWithResult(&db, "PRAGMA page_size"));
RunTransactions(db);
if (open_variant() != OpenVariant::kInMemory) {
db.Close();
EXPECT_EQ(4096, sql::test::ReadDatabasePageSize(db_path_).value_or(-1));
}
}
TEST_P(DatabaseOptionsTest, PageSize_Large) {
static_assert(DatabaseOptions::kDefaultPageSize < 16384,
"The page size numbers in this test file need to change");
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.page_size = 16384,
});
OpenDatabase(db);
EXPECT_EQ("16384", sql::test::ExecuteWithResult(&db, "PRAGMA page_size"));
RunTransactions(db);
if (open_variant() != OpenVariant::kInMemory) {
db.Close();
EXPECT_EQ(16384, sql::test::ReadDatabasePageSize(db_path_).value_or(-1));
}
}
TEST_P(DatabaseOptionsTest, PageSize_Small) {
static_assert(DatabaseOptions::kDefaultPageSize > 1024,
"The page size numbers in this test file need to change");
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.page_size = 1024,
});
OpenDatabase(db);
EXPECT_EQ("1024", sql::test::ExecuteWithResult(&db, "PRAGMA page_size"));
RunTransactions(db);
if (open_variant() != OpenVariant::kInMemory) {
db.Close();
EXPECT_EQ(1024, sql::test::ReadDatabasePageSize(db_path_).value_or(-1));
}
}
TEST_P(DatabaseOptionsTest, CacheSize_Legacy) {
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.cache_size = 0,
});
OpenDatabase(db);
EXPECT_EQ("-2000", sql::test::ExecuteWithResult(&db, "PRAGMA cache_size"));
}
TEST_P(DatabaseOptionsTest, CacheSize_Small) {
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.cache_size = 16,
});
OpenDatabase(db);
EXPECT_EQ("16", sql::test::ExecuteWithResult(&db, "PRAGMA cache_size"));
}
TEST_P(DatabaseOptionsTest, CacheSize_Large) {
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.cache_size = 1000,
});
OpenDatabase(db);
EXPECT_EQ("1000", sql::test::ExecuteWithResult(&db, "PRAGMA cache_size"));
}
TEST_P(DatabaseOptionsTest, EnableViewsDiscouraged_FalseByDefault) {
DatabaseOptions options = {
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
};
EXPECT_FALSE(options.enable_views_discouraged) << "Invalid test assumption";
Database db(options);
OpenDatabase(db);
// sqlite3_db_config() currently only disables querying views. Schema
// operations on views are still allowed.
ASSERT_TRUE(db.Execute("CREATE VIEW view(id) AS SELECT 1"));
{
sql::test::ScopedErrorExpecter expecter;
expecter.ExpectError(SQLITE_ERROR);
Statement select_from_view(db.GetUniqueStatement("SELECT id FROM view"));
EXPECT_FALSE(select_from_view.is_valid());
EXPECT_TRUE(expecter.SawExpectedErrors());
}
// sqlite3_db_config() currently only disables querying views. Schema
// operations on views are still allowed.
EXPECT_TRUE(db.Execute("DROP VIEW IF EXISTS view"));
}
TEST_P(DatabaseOptionsTest, EnableViewsDiscouraged_True) {
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.enable_views_discouraged = true,
});
OpenDatabase(db);
ASSERT_TRUE(db.Execute("CREATE VIEW view(id) AS SELECT 1"));
Statement select_from_view(db.GetUniqueStatement("SELECT id FROM view"));
ASSERT_TRUE(select_from_view.is_valid());
EXPECT_TRUE(select_from_view.Step());
EXPECT_EQ(1, select_from_view.ColumnInt64(0));
EXPECT_TRUE(db.Execute("DROP VIEW IF EXISTS view"));
}
TEST_P(DatabaseOptionsTest, EnableVirtualTablesDiscouraged_FalseByDefault) {
DatabaseOptions options = {
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
};
EXPECT_FALSE(options.enable_virtual_tables_discouraged)
<< "Invalid test assumption";
Database db(options);
OpenDatabase(db);
// sqlite3_prepare_v3() currently only disables accessing virtual tables.
// Schema operations on virtual tables are still allowed.
ASSERT_TRUE(db.Execute(
"CREATE VIRTUAL TABLE fts_table USING fts3(data_table, content TEXT)"));
{
sql::test::ScopedErrorExpecter expecter;
expecter.ExpectError(SQLITE_ERROR);
Statement select_from_vtable(db.GetUniqueStatement(
"SELECT content FROM fts_table WHERE content MATCH 'pattern'"));
EXPECT_FALSE(select_from_vtable.is_valid());
EXPECT_TRUE(expecter.SawExpectedErrors());
}
// sqlite3_prepare_v3() currently only disables accessing virtual tables.
// Schema operations on virtual tables are still allowed.
EXPECT_TRUE(db.Execute("DROP TABLE IF EXISTS fts_table"));
}
TEST_P(DatabaseOptionsTest, EnableVirtualTablesDiscouraged_True) {
Database db(DatabaseOptions{
.exclusive_locking = exclusive_locking(),
.wal_mode = wal_mode(),
.enable_virtual_tables_discouraged = true,
});
OpenDatabase(db);
ASSERT_TRUE(db.Execute(
"CREATE VIRTUAL TABLE fts_table USING fts3(data_table, content TEXT)"));
Statement select_from_vtable(db.GetUniqueStatement(
"SELECT content FROM fts_table WHERE content MATCH 'pattern'"));
ASSERT_TRUE(select_from_vtable.is_valid());
EXPECT_FALSE(select_from_vtable.Step());
EXPECT_TRUE(db.Execute("DROP TABLE IF EXISTS fts_table"));
}
INSTANTIATE_TEST_SUITE_P(
,
DatabaseOptionsTest,
testing::Values(OpenVariant::kInMemory,
OpenVariant::kOnDiskExclusiveJournal,
OpenVariant::kOnDiskNonExclusiveJournal,
OpenVariant::kOnDiskExclusiveWal));
} // namespace
} // namespace sql