chromium/sql/database_unittest.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "sql/database.h"

#include <stddef.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/containers/contains.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/task_environment.h"
#include "base/thread_annotations.h"
#include "base/trace_event/memory_dump_request_args.h"
#include "base/trace_event/process_memory_dump.h"
#include "build/build_config.h"
#include "sql/database_memory_dump_provider.h"
#include "sql/meta_table.h"
#include "sql/recovery.h"
#include "sql/statement.h"
#include "sql/statement_id.h"
#include "sql/test/scoped_error_expecter.h"
#include "sql/test/test_helpers.h"
#include "sql/transaction.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/sqlite/sqlite3.h"

#if BUILDFLAG(IS_WIN)
#include "base/strings/strcat.h"
#endif

namespace sql {

namespace {

ExecuteWithResult;

// Helper to return the count of items in sqlite_schema.  Return -1 in
// case of error.
int SqliteSchemaCount(Database* db) {}

// Handle errors by blowing away the database.
void RazeErrorCallback(Database* db,
                       int expected_error,
                       int error,
                       Statement* stmt) {}

#if BUILDFLAG(IS_POSIX)
// Set a umask and restore the old mask on destruction.  Cribbed from
// shared_memory_unittest.cc.  Used by POSIX-only UserPermission test.
class ScopedUmaskSetter {};
#endif  // BUILDFLAG(IS_POSIX)

bool IsOpenedInCorrectJournalMode(Database* db, bool is_wal) {}

}  // namespace

// We use the parameter to run all tests with WAL mode on and off.
class SQLDatabaseTest : public testing::Test,
                        public testing::WithParamInterface<bool> {};

TEST_P(SQLDatabaseTest, Execute_ValidStatement) {}

TEST_P(SQLDatabaseTest, Execute_InvalidStatement) {}

TEST_P(SQLDatabaseTest, ExecuteScriptForTesting_OneLineValid) {}

TEST_P(SQLDatabaseTest, ExecuteScriptForTesting_OneLineInvalid) {}

TEST_P(SQLDatabaseTest, ExecuteScriptForTesting_ExtraContents) {}

TEST_P(SQLDatabaseTest, ExecuteScriptForTesting_MultipleValidLines) {}

TEST_P(SQLDatabaseTest, ExecuteScriptForTesting_StopsOnCompileError) {}

TEST_P(SQLDatabaseTest, ExecuteScriptForTesting_StopsOnStepError) {}

TEST_P(SQLDatabaseTest, CachedStatement) {}

TEST_P(SQLDatabaseTest, IsSQLValidTest) {}

TEST_P(SQLDatabaseTest, DoesTableExist) {}

TEST_P(SQLDatabaseTest, DoesIndexExist) {}

TEST_P(SQLDatabaseTest, DoesViewExist) {}

TEST_P(SQLDatabaseTest, DoesColumnExist) {}

TEST_P(SQLDatabaseTest, GetLastInsertRowId) {}

// Test the scoped error expecter by attempting to insert a duplicate
// value into an index.
TEST_P(SQLDatabaseTest, ScopedErrorExpecter) {}

TEST_P(SQLDatabaseTest, SchemaIntrospectionUsesErrorExpecter) {}

TEST_P(SQLDatabaseTest, SetErrorCallback) {}

TEST_P(SQLDatabaseTest, SetErrorCallbackDchecksOnExistingCallback) {}

TEST_P(SQLDatabaseTest, ResetErrorCallback) {}

// Regression test for https://crbug.com/1522873
TEST_P(SQLDatabaseTest, ErrorCallbackThatClosesDb) {}

TEST_P(SQLDatabaseTest, DetachFromSequence) {}

// Regression test for https://crbug.com/1522873
TEST_P(SQLDatabaseTest, ErrorCallbackThatFreesDatabase) {}

// Sets a flag to true/false to track being alive.
class LifeTracker {};

// base::BindRepeating() can curry arguments to be passed by const reference to
// the callback function. If the error callback function calls
// reset_error_callback() and the Database doesn't hang onto the callback while
// running it, the storage for those arguments may be deleted while the callback
// function is executing. This test ensures that the database is hanging onto
// the callback while running it.
TEST_P(SQLDatabaseTest, ErrorCallbackStorageProtectedWhileRun) {}

TEST_P(SQLDatabaseTest, Execute_CompilationError) {}

TEST_P(SQLDatabaseTest, GetUniqueStatement_CompilationError) {}

TEST_P(SQLDatabaseTest, GetCachedStatement_CompilationError) {}

TEST_P(SQLDatabaseTest, GetUniqueStatement_ExtraContents) {}

TEST_P(SQLDatabaseTest, GetCachedStatement_ExtraContents) {}

TEST_P(SQLDatabaseTest, IsSQLValid_ExtraContents) {}

TEST_P(SQLDatabaseTest, GetUniqueStatement_NoContents) {}

TEST_P(SQLDatabaseTest, GetCachedStatement_NoContents) {}

TEST_P(SQLDatabaseTest, GetReadonlyStatement) {}

TEST_P(SQLDatabaseTest, IsSQLValid_NoContents) {}

// Test that Database::Raze() results in a database without the
// tables from the original database.
TEST_P(SQLDatabaseTest, Raze) {}

TEST_P(SQLDatabaseTest, RazeDuringSelect) {}

// Helper for SQLDatabaseTest.RazePageSize.  Creates a fresh db based on
// db_prefix, with the given initial page size, and verifies it against the
// expected size.  Then changes to the final page size and razes, verifying that
// the fresh database ends up with the expected final page size.
void TestPageSize(const base::FilePath& db_prefix,
                  int initial_page_size,
                  const std::string& expected_initial_page_size,
                  int final_page_size,
                  const std::string& expected_final_page_size) {}

// Verify that Recovery maintains the page size, and the virtual table
// works with page sizes other than SQLite's default.  Also verify the case
// where the default page size has changed.
TEST_P(SQLDatabaseTest, RazePageSize) {}

// Test that Raze() results are seen in other connections.
TEST_P(SQLDatabaseTest, RazeMultiple) {}

TEST_P(SQLDatabaseTest, Raze_OtherConnectionHasWriteLock) {}

TEST_P(SQLDatabaseTest, Raze_OtherConnectionHasReadLock) {}

TEST_P(SQLDatabaseTest, Raze_EmptyDatabaseFile) {}

// Verify that Raze() can handle a file of junk.
// Need exclusive mode off here as there are some subtleties (by design) around
// how the cache is used with it on which causes the test to fail.
TEST_P(SQLDatabaseTest, RazeNOTADB) {}

// Verify that Raze() can handle a database overwritten with garbage.
TEST_P(SQLDatabaseTest, RazeNOTADB2) {}

// Test that a callback from Open() can raze the database.  This is
// essential for cases where the Open() can fail entirely, so the
// Raze() cannot happen later.  Additionally test that when the
// callback does this during Open(), the open is retried and succeeds.
TEST_P(SQLDatabaseTest, RazeCallbackReopen) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_DeletesData) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_IsOpen) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_Reopen_NoChanges) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_OpenTransaction) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_Preload_NoCrash) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_DoesTableExist) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_IsSQLValid) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_Execute) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_GetUniqueStatement) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_GetCachedStatement) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_InvalidatesUniqueStatement) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_InvalidatesCachedStatement) {}

TEST_P(SQLDatabaseTest, RazeAndPoison_TransactionBegin) {}

TEST_P(SQLDatabaseTest, Close_IsSQLValid) {}

// On Windows, truncate silently fails against a memory-mapped file.  One goal
// of Raze() is to truncate the file to remove blocks which generate I/O errors.
// Test that Raze() turns off memory mapping so that the file is truncated.
// [This would not cover the case of multiple connections where one of the other
// connections is memory-mapped.  That is infrequent in Chromium.]
TEST_P(SQLDatabaseTest, RazeTruncate) {}

#if BUILDFLAG(IS_ANDROID)
TEST_P(SQLDatabaseTest, SetTempDirForSQL) {
  MetaTable meta_table;
  // Below call needs a temporary directory in sqlite3
  // On Android, it can pass only when the temporary directory is set.
  // Otherwise, sqlite3 doesn't find the correct directory to store
  // temporary files and will report the error 'unable to open
  // database file'.
  ASSERT_TRUE(meta_table.Init(db_.get(), 4, 4));
}
#endif  // BUILDFLAG(IS_ANDROID)

TEST_P(SQLDatabaseTest, Delete) {}

#if BUILDFLAG(IS_POSIX)  // This test operates on POSIX file permissions.
TEST_P(SQLDatabaseTest, PosixFilePermissions) {}
#endif  // BUILDFLAG(IS_POSIX)

TEST_P(SQLDatabaseTest, Poison_IsOpen) {}

TEST_P(SQLDatabaseTest, Poison_Close_Reopen_NoChanges) {}

TEST_P(SQLDatabaseTest, Poison_Preload_NoCrash) {}

TEST_P(SQLDatabaseTest, Poison_DoesTableExist) {}

TEST_P(SQLDatabaseTest, Poison_IsSQLValid) {}

TEST_P(SQLDatabaseTest, Poison_Execute) {}

TEST_P(SQLDatabaseTest, Poison_GetUniqueStatement) {}

TEST_P(SQLDatabaseTest, Poison_GetCachedStatement) {}

TEST_P(SQLDatabaseTest, Poison_InvalidatesUniqueStatement) {}

TEST_P(SQLDatabaseTest, Poison_InvalidatesCachedStatement) {}

TEST_P(SQLDatabaseTest, Poison_TransactionBegin) {}

TEST_P(SQLDatabaseTest, Poison_OpenTransaction) {}

TEST_P(SQLDatabaseTest, AttachDatabase) {}

TEST_P(SQLDatabaseTest, AttachDatabaseWithOpenTransaction) {}

TEST_P(SQLDatabaseTest, FullIntegrityCheck) {}

TEST_P(SQLDatabaseTest, OnMemoryDump) {}

// Test that the functions to collect diagnostic data run to completion, without
// worrying too much about what they generate (since that will change).
TEST_P(SQLDatabaseTest, CollectDiagnosticInfo) {}

// Test that a fresh database has mmap enabled by default, if mmap'ed I/O is
// enabled by SQLite.
TEST_P(SQLDatabaseTest, MmapInitiallyEnabled) {}

// Test whether a fresh database gets mmap enabled when using alternate status
// storage.
TEST_P(SQLDatabaseTest, MmapInitiallyEnabledAltStatus) {}

TEST_P(SQLDatabaseTest, ComputeMmapSizeForOpen) {}

TEST_P(SQLDatabaseTest, ComputeMmapSizeForOpenAltStatus) {}

TEST_P(SQLDatabaseTest, GetMemoryUsage) {}

TEST_P(SQLDatabaseTest, DoubleQuotedStringLiteralsDisabledByDefault) {}

TEST_P(SQLDatabaseTest, ForeignKeyEnforcementDisabledByDefault) {}

TEST_P(SQLDatabaseTest, TriggersDisabledByDefault) {}

// This test ensures that a database can be open/create with a journal mode and
// can be re-open later with a different journal mode.
TEST_P(SQLDatabaseTest, ReOpenWithDifferentJournalMode) {}

#if BUILDFLAG(IS_WIN)

class SQLDatabaseTestExclusiveFileLockMode
    : public testing::Test,
      public testing::WithParamInterface<::testing::tuple<bool, bool>> {
 public:
  ~SQLDatabaseTestExclusiveFileLockMode() override = default;

  void SetUp() override {
    db_ = std::make_unique<Database>(GetDBOptions());
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    db_path_ = temp_dir_.GetPath().AppendASCII("maybelocked.sqlite");
    ASSERT_TRUE(db_->Open(db_path_));
  }

  DatabaseOptions GetDBOptions() {
    DatabaseOptions options;
    options.wal_mode = IsWALEnabled();
    options.exclusive_locking = true;
    options.exclusive_database_file_lock = IsExclusivelockEnabled();
    return options;
  }

  bool IsWALEnabled() { return std::get<0>(GetParam()); }
  bool IsExclusivelockEnabled() { return std::get<1>(GetParam()); }

 protected:
  base::ScopedTempDir temp_dir_;
  base::FilePath db_path_;
  std::unique_ptr<Database> db_;
};

TEST_P(SQLDatabaseTestExclusiveFileLockMode, BasicStatement) {
  ASSERT_TRUE(db_->Execute("CREATE TABLE data(contents TEXT)"));
  EXPECT_EQ(SQLITE_OK, db_->GetErrorCode());

  ASSERT_TRUE(base::PathExists(db_path_));
  base::File open_db(db_path_, base::File::Flags::FLAG_OPEN_ALWAYS |
                                   base::File::Flags::FLAG_READ);

  // If exclusive lock is enabled, then the test should not be able to re-open
  // the database file, on Windows only.
  EXPECT_EQ(IsExclusivelockEnabled(), !open_db.IsValid());
}

INSTANTIATE_TEST_SUITE_P(
    All,
    SQLDatabaseTestExclusiveFileLockMode,
    ::testing::Combine(::testing::Bool(), ::testing::Bool()),
    [](const auto& info) {
      return base::StrCat(
          {std::get<0>(info.param) ? "WALEnabled" : "WALDisabled",
           std::get<1>(info.param) ? "ExclusiveLock" : "NoExclusiveLock"});
    });

#else

TEST(SQLInvalidDatabaseFlagsDeathTest, ExclusiveDatabaseLock) {}

#endif  // BUILDFLAG(IS_WIN)

class SQLDatabaseTestExclusiveMode : public testing::Test,
                                     public testing::WithParamInterface<bool> {};

TEST_P(SQLDatabaseTestExclusiveMode, LockingModeExclusive) {}

TEST_P(SQLDatabaseTest, LockingModeNormal) {}

TEST_P(SQLDatabaseTest, OpenedInCorrectMode) {}

TEST_P(SQLDatabaseTest, CheckpointDatabase) {}

TEST_P(SQLDatabaseTest, OpenFailsAfterCorruptSizeInHeader) {}

TEST_P(SQLDatabaseTest, OpenWithRecoveryHandlesCorruption) {}

TEST_P(SQLDatabaseTest, ExecuteFailsAfterCorruptSizeInHeader) {}

TEST_P(SQLDatabaseTest, SchemaFailsAfterCorruptSizeInHeader) {}

TEST(SQLEmptyPathDatabaseTest, EmptyPathTest) {}

// WAL mode is currently not supported on Fuchsia.
#if !BUILDFLAG(IS_FUCHSIA)
INSTANTIATE_TEST_SUITE_P();
INSTANTIATE_TEST_SUITE_P();
#else
INSTANTIATE_TEST_SUITE_P(JournalMode, SQLDatabaseTest, testing::Values(false));
INSTANTIATE_TEST_SUITE_P(JournalMode,
                         SQLDatabaseTestExclusiveMode,
                         testing::Values(false));
#endif
}  // namespace sql