// 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