blob: e6c2900d6005c41cfa36de8027b0ef7c404229d9 [file] [edit]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/disk_cache/sql/sql_persistent_store_queries.h"
#include <cstdint>
#include <memory>
#include <string_view>
#include "base/containers/fixed_flat_map.h"
#include "base/containers/fixed_flat_set.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/task/thread_pool.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "net/base/cache_type.h"
#include "net/disk_cache/sql/sql_async_task_manager.h"
#include "net/disk_cache/sql/sql_backend_constants.h"
#include "net/disk_cache/sql/sql_persistent_store.h"
#include "net/disk_cache/sql/test_util.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace disk_cache_sql_queries {
// Defines the set of queries that are used for schema and index creation. These
// queries are not suitable for checking plans against an already-initialized
// database.
constexpr auto kSchemaAndIndexQueries = base::MakeFixedFlatSet<Query>({
Query::kInitSchema_CreateTableResources,
Query::kInitSchema_CreateTableBlobs,
Query::kIndex_ResourcesCacheKeyHashDoomed,
Query::kIndex_LiveResourcesLastUsed,
Query::kIndex_LiveResourcesHints,
Query::kIndex_BlobsResIdStart,
});
class SqlPersistentStoreQueriesTest : public testing::Test {
protected:
void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
// Creates a database file with the correct schema in the temporary directory.
// This is done by instantiating and initializing a SqlPersistentStore, which
// handles schema creation, and then closing it to ensure all changes are
// flushed to disk.
void CreateDatabaseInTemDir() {
const base::FilePath path = temp_dir_.GetPath();
const int64_t kDefaultMaxBytes = 10 * 1024 * 1024;
auto background_task_runner =
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()});
// Create and initialize a store to create the DB file with schema.
auto store = std::make_unique<disk_cache::SqlPersistentStore>(
path, kDefaultMaxBytes, net::CacheType::DISK_CACHE,
std::vector<scoped_refptr<base::SequencedTaskRunner>>(
{background_task_runner}),
async_task_manager_);
base::test::TestFuture<disk_cache::SqlPersistentStore::Error> future;
store->Initialize(future.GetCallback());
ASSERT_EQ(future.Get(), disk_cache::SqlPersistentStore::Error::kOk);
// Close the store by destroying it. This ensures all data is flushed.
store.reset();
// Wait for background tasks to finish by posting a task to the runner and
// waiting for it to execute.
base::RunLoop run_loop;
background_task_runner->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
// Executes `EXPLAIN QUERY PLAN` for a given SQL query using the
// `sqlite_dev_shell` tool. This allows verifying that the query optimizer is
// using the expected indexes, which is critical for performance.
std::string GetQueryPlan(base::cstring_view query) {
return disk_cache::test::GetQueryPlan(
temp_dir_.GetPath().Append(
disk_cache::kSqlBackendDatabaseShard0FileName),
query);
}
base::ScopedTempDir temp_dir_;
private:
base::test::TaskEnvironment task_environment_;
disk_cache::SqlAsyncTaskManager async_task_manager_;
};
// This test verifies that critical SQL queries use the intended indexes by
// checking their query plans. This is essential for ensuring the performance of
// database operations. A query that performs a full table scan instead of using
// an index can lead to significant performance degradation.
TEST_F(SqlPersistentStoreQueriesTest, AllQueriesHaveValidPlan) {
CreateDatabaseInTemDir();
// Defines the expected query plan for each query. An empty plan indicates
// that no specific plan is expected (e.g., for INSERT statements), or that
// the query is a schema-related query that cannot be explained.
constexpr auto kAllQueriesAndPlans =
base::MakeFixedFlatMap<Query, std::string_view>(
{{Query::kOpenEntry_SelectLiveResources,
"`--SEARCH resources USING "
"INDEX index_resources_cache_key_hash_doomed "
"(cache_key_hash=? AND doomed=?)"},
{Query::kCreateEntry_InsertIntoResources, ""},
{Query::kDoomEntry_MarkDoomedResources,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kDeleteDoomedEntry_DeleteFromResources,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kDeleteLiveEntry_DeleteFromResources,
"`--SEARCH resources USING "
"INDEX index_resources_cache_key_hash_doomed "
"(cache_key_hash=? AND doomed=?)"},
{Query::kDeleteAllEntries_DeleteFromResources, ""},
{Query::kDeleteAllEntries_DeleteFromBlobs, ""},
{Query::kDeleteLiveEntriesBetween_SelectLiveResources,
"`--SEARCH resources USING "
"COVERING INDEX index_live_resources_last_used_bytes_usage "
"(last_used>? AND last_used<?)"},
{Query::kDeleteResourceByResIds_DeleteFromResources,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kDeleteLiveResourceByResIdReturnUsage,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kUpdateEntryLastUsedByKey_UpdateResourceLastUsed,
"`--SEARCH resources USING "
"INDEX index_resources_cache_key_hash_doomed "
"(cache_key_hash=? AND doomed=?)"},
{Query::kInsertIntoResources, ""},
{Query::kUpdateLastUsed,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kUpdateLastUsedHeader,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kUpdateLastUsedHeaderHints,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kUpdateLastUsedHints,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kUpdateLastUsedBody,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kUpdateLastUsedBodyHints,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kUpdateLastUsedBodyHeader,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kUpdateLastUsedBodyHeaderHints,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kWriteEntryData_UpdateResource,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kTrimOverlappingBlobs_DeleteContained,
"`--SEARCH blobs USING "
"INDEX index_blobs_res_id_start "
"(res_id=? AND start>?)"},
{Query::kTrimOverlappingBlobs_SelectOverlapping,
"`--SEARCH blobs USING "
"INDEX index_blobs_res_id_start "
"(res_id=? AND start<?)"},
{Query::kTruncateBlobsAfter_DeleteAfter,
"`--SEARCH blobs USING "
"COVERING INDEX index_blobs_res_id_start "
"(res_id=? AND start>?)"},
{Query::kInsertNewBlob_InsertIntoBlobs, ""},
{Query::kDeleteBlobById_DeleteFromBlobs,
"`--SEARCH blobs USING "
"INTEGER PRIMARY KEY (rowid=?)"},
{Query::kDeleteBlobsByResId_DeleteFromBlobs,
"`--SEARCH blobs USING "
"INDEX index_blobs_res_id_start "
"(res_id=?)"},
{Query::kReadEntryData_SelectOverlapping,
"`--SEARCH blobs USING "
"INDEX index_blobs_res_id_start "
"(res_id=? AND start<?)"},
{Query::kGetEntryAvailableRange_SelectOverlapping,
"`--SEARCH blobs USING "
"INDEX index_blobs_res_id_start "
"(res_id=? AND start<?)"},
{Query::kCalculateSizeOfEntriesBetween_SelectLiveResources,
"`--SEARCH resources USING "
"COVERING INDEX index_live_resources_last_used_bytes_usage "
"(last_used>? AND last_used<?)"},
{Query::kOpenNextEntry_SelectLiveResources,
"`--SEARCH resources USING "
"INTEGER PRIMARY KEY (rowid<?)"},
{Query::kStartEviction_SelectLiveResources,
"`--SCAN resources USING "
"COVERING INDEX index_live_resources_last_used_bytes_usage"},
{Query::kCalculateResourceEntryCount_SelectCountFromLiveResources,
"`--SCAN resources USING "
"COVERING INDEX index_live_resources_last_used_bytes_usage"},
{Query::kCalculateTotalSize_SelectTotalSizeFromLiveResources,
"`--SCAN resources USING "
"COVERING INDEX index_live_resources_last_used_bytes_usage"},
{Query::kLoadInMemoryIndex_SelectCacheKeyHashFromLiveResources,
"`--SCAN resources USING COVERING INDEX "
"index_resources_cache_key_hash_doomed"},
{Query::kLoadInMemoryIndex_SelectHintsFromLiveResources,
"`--SCAN resources USING COVERING INDEX "
"index_live_resources_hints"}});
static_assert(kAllQueriesAndPlans.size() + kSchemaAndIndexQueries.size() ==
static_cast<int>(Query::kMaxValue) + 1);
for (const auto& it : kAllQueriesAndPlans) {
const base::cstring_view query_string = GetQuery(it.first);
SCOPED_TRACE(query_string);
EXPECT_EQ(GetQueryPlan(query_string), it.second);
}
}
} // namespace disk_cache_sql_queries