| // 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 <optional> | |
| #include "base/files/file_path.h" | |
| #include "base/files/scoped_temp_dir.h" | |
| #include "sql/database.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/sqlite/sqlite3.h" | |
| namespace sql { | |
| namespace { | |
| enum class OpenVariant { | |
| kInMemory = 1, | |
| kOnDiskExclusiveJournal = 2, | |
| kOnDiskNonExclusiveJournal = 3, | |
| kOnDiskExclusiveWal = 4, | |
| }; | |
| } // namespace | |
| // 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 = DatabaseOptions() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()); | |
| EXPECT_FALSE(options.flush_to_media_) << "Invalid test assumption"; | |
| Database db(options, test::kTestTag); | |
| OpenDatabase(db); | |
| EXPECT_EQ("0", sql::test::ExecuteWithResult(&db, "PRAGMA fullfsync")); | |
| } | |
| TEST_P(DatabaseOptionsTest, FlushToDisk_True) { | |
| Database db(DatabaseOptions() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()) | |
| .set_flush_to_media(true), | |
| test::kTestTag); | |
| OpenDatabase(db); | |
| EXPECT_EQ("1", sql::test::ExecuteWithResult(&db, "PRAGMA fullfsync")); | |
| } | |
| TEST_P(DatabaseOptionsTest, FlushToDisk_False_DoesNotCrash) { | |
| Database db(DatabaseOptions() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()) | |
| .set_flush_to_media(false), | |
| test::kTestTag); | |
| 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() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()) | |
| .set_flush_to_media(true), | |
| test::kTestTag); | |
| 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() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()) | |
| .set_page_size(4096), | |
| test::kTestTag); | |
| 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() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()) | |
| .set_page_size(16384), | |
| test::kTestTag); | |
| 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() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()) | |
| .set_page_size(1024), | |
| test::kTestTag); | |
| 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() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()) | |
| .set_cache_size(0), | |
| test::kTestTag); | |
| OpenDatabase(db); | |
| EXPECT_EQ("-2000", sql::test::ExecuteWithResult(&db, "PRAGMA cache_size")); | |
| } | |
| TEST_P(DatabaseOptionsTest, CacheSize_Small) { | |
| Database db(DatabaseOptions() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()) | |
| .set_cache_size(16), | |
| test::kTestTag); | |
| OpenDatabase(db); | |
| EXPECT_EQ("16", sql::test::ExecuteWithResult(&db, "PRAGMA cache_size")); | |
| } | |
| TEST_P(DatabaseOptionsTest, CacheSize_Large) { | |
| Database db(DatabaseOptions() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()) | |
| .set_cache_size(1000), | |
| test::kTestTag); | |
| OpenDatabase(db); | |
| EXPECT_EQ("1000", sql::test::ExecuteWithResult(&db, "PRAGMA cache_size")); | |
| } | |
| TEST_P(DatabaseOptionsTest, EnableViewsDiscouraged_FalseByDefault) { | |
| DatabaseOptions options = DatabaseOptions() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()); | |
| EXPECT_FALSE(options.enable_views_discouraged_) << "Invalid test assumption"; | |
| Database db(options, test::kTestTag); | |
| 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() | |
| .set_exclusive_locking(exclusive_locking()) | |
| .set_wal_mode(wal_mode()) | |
| .set_enable_views_discouraged(true), | |
| test::kTestTag); | |
| 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")); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| , | |
| DatabaseOptionsTest, | |
| testing::Values(OpenVariant::kInMemory, | |
| OpenVariant::kOnDiskExclusiveJournal, | |
| OpenVariant::kOnDiskNonExclusiveJournal, | |
| OpenVariant::kOnDiskExclusiveWal)); | |
| } // namespace sql |