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