@ -0,0 +1,541 @@ | |||
# Copyright 2017 The LevelDB Authors. All rights reserved. | |||
# Use of this source code is governed by a BSD-style license that can be | |||
# found in the LICENSE file. See the AUTHORS file for names of contributors. | |||
cmake_minimum_required(VERSION 3.9) | |||
# Keep the version below in sync with the one in db.h | |||
project(leveldb VERSION 1.23.0 LANGUAGES C CXX) | |||
# C standard can be overridden when this is used as a sub-project. | |||
if(NOT CMAKE_C_STANDARD) | |||
# This project can use C11, but will gracefully decay down to C89. | |||
set(CMAKE_C_STANDARD 11) | |||
set(CMAKE_C_STANDARD_REQUIRED OFF) | |||
set(CMAKE_C_EXTENSIONS OFF) | |||
endif(NOT CMAKE_C_STANDARD) | |||
# C++ standard can be overridden when this is used as a sub-project. | |||
if(NOT CMAKE_CXX_STANDARD) | |||
# This project requires C++11. | |||
set(CMAKE_CXX_STANDARD 11) | |||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | |||
set(CMAKE_CXX_EXTENSIONS OFF) | |||
endif(NOT CMAKE_CXX_STANDARD) | |||
if (WIN32) | |||
set(LEVELDB_PLATFORM_NAME LEVELDB_PLATFORM_WINDOWS) | |||
# TODO(cmumford): Make UNICODE configurable for Windows. | |||
add_definitions(-D_UNICODE -DUNICODE) | |||
else (WIN32) | |||
set(LEVELDB_PLATFORM_NAME LEVELDB_PLATFORM_POSIX) | |||
endif (WIN32) | |||
option(LEVELDB_BUILD_TESTS "Build LevelDB's unit tests" ON) | |||
option(LEVELDB_BUILD_BENCHMARKS "Build LevelDB's benchmarks" ON) | |||
option(LEVELDB_INSTALL "Install LevelDB's header and library" ON) | |||
include(CheckIncludeFile) | |||
check_include_file("unistd.h" HAVE_UNISTD_H) | |||
include(CheckLibraryExists) | |||
check_library_exists(crc32c crc32c_value "" HAVE_CRC32C) | |||
check_library_exists(snappy snappy_compress "" HAVE_SNAPPY) | |||
check_library_exists(zstd zstd_compress "" HAVE_ZSTD) | |||
check_library_exists(tcmalloc malloc "" HAVE_TCMALLOC) | |||
include(CheckCXXSymbolExists) | |||
# Using check_cxx_symbol_exists() instead of check_c_symbol_exists() because | |||
# we're including the header from C++, and feature detection should use the same | |||
# compiler language that the project will use later. Principles aside, some | |||
# versions of do not expose fdatasync() in <unistd.h> in standard C mode | |||
# (-std=c11), but do expose the function in standard C++ mode (-std=c++11). | |||
check_cxx_symbol_exists(fdatasync "unistd.h" HAVE_FDATASYNC) | |||
check_cxx_symbol_exists(F_FULLFSYNC "fcntl.h" HAVE_FULLFSYNC) | |||
check_cxx_symbol_exists(O_CLOEXEC "fcntl.h" HAVE_O_CLOEXEC) | |||
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") | |||
# Disable C++ exceptions. | |||
string(REGEX REPLACE "/EH[a-z]+" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") | |||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHs-c-") | |||
add_definitions(-D_HAS_EXCEPTIONS=0) | |||
# Disable RTTI. | |||
string(REGEX REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") | |||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-") | |||
else(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") | |||
# Enable strict prototype warnings for C code in clang and gcc. | |||
if(NOT CMAKE_C_FLAGS MATCHES "-Wstrict-prototypes") | |||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wstrict-prototypes") | |||
endif(NOT CMAKE_C_FLAGS MATCHES "-Wstrict-prototypes") | |||
# Disable C++ exceptions. | |||
string(REGEX REPLACE "-fexceptions" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") | |||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") | |||
# Disable RTTI. | |||
string(REGEX REPLACE "-frtti" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") | |||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") | |||
endif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") | |||
# Test whether -Wthread-safety is available. See | |||
# https://clang.llvm.org/docs/ThreadSafetyAnalysis.html | |||
include(CheckCXXCompilerFlag) | |||
check_cxx_compiler_flag(-Wthread-safety HAVE_CLANG_THREAD_SAFETY) | |||
# Used by googletest. | |||
check_cxx_compiler_flag(-Wno-missing-field-initializers | |||
LEVELDB_HAVE_NO_MISSING_FIELD_INITIALIZERS) | |||
include(CheckCXXSourceCompiles) | |||
# Test whether C++17 __has_include is available. | |||
check_cxx_source_compiles(" | |||
#if defined(__has_include) && __has_include(<string>) | |||
#include <string> | |||
#endif | |||
int main() { std::string str; return 0; } | |||
" HAVE_CXX17_HAS_INCLUDE) | |||
set(LEVELDB_PUBLIC_INCLUDE_DIR "include/leveldb") | |||
set(LEVELDB_PORT_CONFIG_DIR "include/port") | |||
configure_file( | |||
"port/port_config.h.in" | |||
"${PROJECT_BINARY_DIR}/${LEVELDB_PORT_CONFIG_DIR}/port_config.h" | |||
) | |||
include_directories( | |||
"${PROJECT_BINARY_DIR}/include" | |||
"." | |||
) | |||
if(BUILD_SHARED_LIBS) | |||
# Only export LEVELDB_EXPORT symbols from the shared library. | |||
add_compile_options(-fvisibility=hidden) | |||
endif(BUILD_SHARED_LIBS) | |||
# Must be included before CMAKE_INSTALL_INCLUDEDIR is used. | |||
include(GNUInstallDirs) | |||
add_library(leveldb "") | |||
target_sources(leveldb | |||
PRIVATE | |||
"${PROJECT_BINARY_DIR}/${LEVELDB_PORT_CONFIG_DIR}/port_config.h" | |||
"db/builder.cc" | |||
"db/builder.h" | |||
"db/c.cc" | |||
"db/db_impl.cc" | |||
"db/db_impl.h" | |||
"db/db_iter.cc" | |||
"db/db_iter.h" | |||
"db/dbformat.cc" | |||
"db/dbformat.h" | |||
"db/dumpfile.cc" | |||
"db/filename.cc" | |||
"db/filename.h" | |||
"db/log_format.h" | |||
"db/log_reader.cc" | |||
"db/log_reader.h" | |||
"db/log_writer.cc" | |||
"db/log_writer.h" | |||
"db/memtable.cc" | |||
"db/memtable.h" | |||
"db/repair.cc" | |||
"db/skiplist.h" | |||
"db/snapshot.h" | |||
"db/table_cache.cc" | |||
"db/table_cache.h" | |||
"db/version_edit.cc" | |||
"db/version_edit.h" | |||
"db/version_set.cc" | |||
"db/version_set.h" | |||
"db/write_batch_internal.h" | |||
"db/write_batch.cc" | |||
"port/port_stdcxx.h" | |||
"port/port.h" | |||
"port/thread_annotations.h" | |||
"table/block_builder.cc" | |||
"table/block_builder.h" | |||
"table/block.cc" | |||
"table/block.h" | |||
"table/filter_block.cc" | |||
"table/filter_block.h" | |||
"table/format.cc" | |||
"table/format.h" | |||
"table/iterator_wrapper.h" | |||
"table/iterator.cc" | |||
"table/merger.cc" | |||
"table/merger.h" | |||
"table/table_builder.cc" | |||
"table/table.cc" | |||
"table/two_level_iterator.cc" | |||
"table/two_level_iterator.h" | |||
"util/arena.cc" | |||
"util/arena.h" | |||
"util/bloom.cc" | |||
"util/cache.cc" | |||
"util/coding.cc" | |||
"util/coding.h" | |||
"util/comparator.cc" | |||
"util/crc32c.cc" | |||
"util/crc32c.h" | |||
"util/env.cc" | |||
"util/filter_policy.cc" | |||
"util/hash.cc" | |||
"util/hash.h" | |||
"util/logging.cc" | |||
"util/logging.h" | |||
"util/mutexlock.h" | |||
"util/no_destructor.h" | |||
"util/options.cc" | |||
"util/random.h" | |||
"util/status.cc" | |||
# Only CMake 3.3+ supports PUBLIC sources in targets exported by "install". | |||
$<$<VERSION_GREATER:CMAKE_VERSION,3.2>:PUBLIC> | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/c.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/cache.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/comparator.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/db.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/dumpfile.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/env.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/export.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/filter_policy.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/iterator.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/options.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/slice.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/status.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/table_builder.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/table.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/write_batch.h" | |||
) | |||
if (WIN32) | |||
target_sources(leveldb | |||
PRIVATE | |||
"util/env_windows.cc" | |||
"util/windows_logger.h" | |||
) | |||
else (WIN32) | |||
target_sources(leveldb | |||
PRIVATE | |||
"util/env_posix.cc" | |||
"util/posix_logger.h" | |||
) | |||
endif (WIN32) | |||
# MemEnv is not part of the interface and could be pulled to a separate library. | |||
target_sources(leveldb | |||
PRIVATE | |||
"helpers/memenv/memenv.cc" | |||
"helpers/memenv/memenv.h" | |||
) | |||
target_include_directories(leveldb | |||
PUBLIC | |||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> | |||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> | |||
) | |||
set_target_properties(leveldb | |||
PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) | |||
target_compile_definitions(leveldb | |||
PRIVATE | |||
# Used by include/export.h when building shared libraries. | |||
LEVELDB_COMPILE_LIBRARY | |||
# Used by port/port.h. | |||
${LEVELDB_PLATFORM_NAME}=1 | |||
) | |||
if (NOT HAVE_CXX17_HAS_INCLUDE) | |||
target_compile_definitions(leveldb | |||
PRIVATE | |||
LEVELDB_HAS_PORT_CONFIG_H=1 | |||
) | |||
endif(NOT HAVE_CXX17_HAS_INCLUDE) | |||
if(BUILD_SHARED_LIBS) | |||
target_compile_definitions(leveldb | |||
PUBLIC | |||
# Used by include/export.h. | |||
LEVELDB_SHARED_LIBRARY | |||
) | |||
endif(BUILD_SHARED_LIBS) | |||
if(HAVE_CLANG_THREAD_SAFETY) | |||
target_compile_options(leveldb | |||
PUBLIC | |||
-Werror -Wthread-safety) | |||
endif(HAVE_CLANG_THREAD_SAFETY) | |||
if(HAVE_CRC32C) | |||
target_link_libraries(leveldb crc32c) | |||
endif(HAVE_CRC32C) | |||
if(HAVE_SNAPPY) | |||
target_link_libraries(leveldb snappy) | |||
endif(HAVE_SNAPPY) | |||
if(HAVE_ZSTD) | |||
target_link_libraries(leveldb zstd) | |||
endif(HAVE_ZSTD) | |||
if(HAVE_TCMALLOC) | |||
target_link_libraries(leveldb tcmalloc) | |||
endif(HAVE_TCMALLOC) | |||
# Needed by port_stdcxx.h | |||
find_package(Threads REQUIRED) | |||
target_link_libraries(leveldb Threads::Threads) | |||
add_executable(leveldbutil | |||
"db/leveldbutil.cc" | |||
) | |||
target_link_libraries(leveldbutil leveldb) | |||
if(LEVELDB_BUILD_TESTS) | |||
enable_testing() | |||
# Prevent overriding the parent project's compiler/linker settings on Windows. | |||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) | |||
set(install_gtest OFF) | |||
set(install_gmock OFF) | |||
set(build_gmock ON) | |||
# This project is tested using GoogleTest. | |||
add_subdirectory("third_party/googletest") | |||
# GoogleTest triggers a missing field initializers warning. | |||
if(LEVELDB_HAVE_NO_MISSING_FIELD_INITIALIZERS) | |||
set_property(TARGET gtest | |||
APPEND PROPERTY COMPILE_OPTIONS -Wno-missing-field-initializers) | |||
set_property(TARGET gmock | |||
APPEND PROPERTY COMPILE_OPTIONS -Wno-missing-field-initializers) | |||
endif(LEVELDB_HAVE_NO_MISSING_FIELD_INITIALIZERS) | |||
add_executable(leveldb_tests "") | |||
target_sources(leveldb_tests | |||
PRIVATE | |||
# "db/fault_injection_test.cc" | |||
# "issues/issue178_test.cc" | |||
# "issues/issue200_test.cc" | |||
# "issues/issue320_test.cc" | |||
"${PROJECT_BINARY_DIR}/${LEVELDB_PORT_CONFIG_DIR}/port_config.h" | |||
# "util/env_test.cc" | |||
"util/status_test.cc" | |||
"util/no_destructor_test.cc" | |||
"util/testutil.cc" | |||
"util/testutil.h" | |||
) | |||
if(NOT BUILD_SHARED_LIBS) | |||
target_sources(leveldb_tests | |||
PRIVATE | |||
"db/autocompact_test.cc" | |||
"db/corruption_test.cc" | |||
"db/db_test.cc" | |||
"db/dbformat_test.cc" | |||
"db/filename_test.cc" | |||
"db/log_test.cc" | |||
"db/recovery_test.cc" | |||
"db/skiplist_test.cc" | |||
"db/version_edit_test.cc" | |||
"db/version_set_test.cc" | |||
"db/write_batch_test.cc" | |||
"helpers/memenv/memenv_test.cc" | |||
"table/filter_block_test.cc" | |||
"table/table_test.cc" | |||
"util/arena_test.cc" | |||
"util/bloom_test.cc" | |||
"util/cache_test.cc" | |||
"util/coding_test.cc" | |||
"util/crc32c_test.cc" | |||
"util/hash_test.cc" | |||
"util/logging_test.cc" | |||
) | |||
endif(NOT BUILD_SHARED_LIBS) | |||
target_link_libraries(leveldb_tests leveldb gmock gtest gtest_main) | |||
target_compile_definitions(leveldb_tests | |||
PRIVATE | |||
${LEVELDB_PLATFORM_NAME}=1 | |||
) | |||
if (NOT HAVE_CXX17_HAS_INCLUDE) | |||
target_compile_definitions(leveldb_tests | |||
PRIVATE | |||
LEVELDB_HAS_PORT_CONFIG_H=1 | |||
) | |||
endif(NOT HAVE_CXX17_HAS_INCLUDE) | |||
add_test(NAME "leveldb_tests" COMMAND "leveldb_tests") | |||
function(leveldb_test test_file) | |||
get_filename_component(test_target_name "${test_file}" NAME_WE) | |||
add_executable("${test_target_name}" "") | |||
target_sources("${test_target_name}" | |||
PRIVATE | |||
"${PROJECT_BINARY_DIR}/${LEVELDB_PORT_CONFIG_DIR}/port_config.h" | |||
"util/testutil.cc" | |||
"util/testutil.h" | |||
"${test_file}" | |||
) | |||
target_link_libraries("${test_target_name}" leveldb gmock gtest) | |||
target_compile_definitions("${test_target_name}" | |||
PRIVATE | |||
${LEVELDB_PLATFORM_NAME}=1 | |||
) | |||
if (NOT HAVE_CXX17_HAS_INCLUDE) | |||
target_compile_definitions("${test_target_name}" | |||
PRIVATE | |||
LEVELDB_HAS_PORT_CONFIG_H=1 | |||
) | |||
endif(NOT HAVE_CXX17_HAS_INCLUDE) | |||
add_test(NAME "${test_target_name}" COMMAND "${test_target_name}") | |||
endfunction(leveldb_test) | |||
leveldb_test("db/c_test.c") | |||
if(NOT BUILD_SHARED_LIBS) | |||
# TODO(costan): This test also uses | |||
# "util/env_{posix|windows}_test_helper.h" | |||
if (WIN32) | |||
leveldb_test("util/env_windows_test.cc") | |||
else (WIN32) | |||
leveldb_test("util/env_posix_test.cc") | |||
endif (WIN32) | |||
endif(NOT BUILD_SHARED_LIBS) | |||
endif(LEVELDB_BUILD_TESTS) | |||
if(LEVELDB_BUILD_BENCHMARKS) | |||
# This project uses Google benchmark for benchmarking. | |||
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) | |||
set(BENCHMARK_ENABLE_EXCEPTIONS OFF CACHE BOOL "" FORCE) | |||
add_subdirectory("third_party/benchmark") | |||
function(leveldb_benchmark bench_file) | |||
get_filename_component(bench_target_name "${bench_file}" NAME_WE) | |||
add_executable("${bench_target_name}" "") | |||
target_sources("${bench_target_name}" | |||
PRIVATE | |||
"${PROJECT_BINARY_DIR}/${LEVELDB_PORT_CONFIG_DIR}/port_config.h" | |||
"util/histogram.cc" | |||
"util/histogram.h" | |||
"util/testutil.cc" | |||
"util/testutil.h" | |||
"${bench_file}" | |||
) | |||
target_link_libraries("${bench_target_name}" leveldb gmock gtest benchmark) | |||
target_compile_definitions("${bench_target_name}" | |||
PRIVATE | |||
${LEVELDB_PLATFORM_NAME}=1 | |||
) | |||
if (NOT HAVE_CXX17_HAS_INCLUDE) | |||
target_compile_definitions("${bench_target_name}" | |||
PRIVATE | |||
LEVELDB_HAS_PORT_CONFIG_H=1 | |||
) | |||
endif(NOT HAVE_CXX17_HAS_INCLUDE) | |||
endfunction(leveldb_benchmark) | |||
if(NOT BUILD_SHARED_LIBS) | |||
leveldb_benchmark("benchmarks/db_bench.cc") | |||
endif(NOT BUILD_SHARED_LIBS) | |||
check_library_exists(sqlite3 sqlite3_open "" HAVE_SQLITE3) | |||
if(HAVE_SQLITE3) | |||
leveldb_benchmark("benchmarks/db_bench_sqlite3.cc") | |||
target_link_libraries(db_bench_sqlite3 sqlite3) | |||
endif(HAVE_SQLITE3) | |||
# check_library_exists is insufficient here because the library names have | |||
# different manglings when compiled with clang or gcc, at least when installed | |||
# with Homebrew on Mac. | |||
set(OLD_CMAKE_REQURED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) | |||
list(APPEND CMAKE_REQUIRED_LIBRARIES kyotocabinet) | |||
check_cxx_source_compiles(" | |||
#include <kcpolydb.h> | |||
int main() { | |||
kyotocabinet::TreeDB* db = new kyotocabinet::TreeDB(); | |||
delete db; | |||
return 0; | |||
} | |||
" HAVE_KYOTOCABINET) | |||
set(CMAKE_REQUIRED_LIBRARIES ${OLD_CMAKE_REQURED_LIBRARIES}) | |||
if(HAVE_KYOTOCABINET) | |||
leveldb_benchmark("benchmarks/db_bench_tree_db.cc") | |||
target_link_libraries(db_bench_tree_db kyotocabinet) | |||
endif(HAVE_KYOTOCABINET) | |||
endif(LEVELDB_BUILD_BENCHMARKS) | |||
if(LEVELDB_INSTALL) | |||
install(TARGETS leveldb | |||
EXPORT leveldbTargets | |||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} | |||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} | |||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} | |||
) | |||
install( | |||
FILES | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/c.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/cache.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/comparator.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/db.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/dumpfile.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/env.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/export.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/filter_policy.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/iterator.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/options.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/slice.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/status.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/table_builder.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/table.h" | |||
"${LEVELDB_PUBLIC_INCLUDE_DIR}/write_batch.h" | |||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/leveldb" | |||
) | |||
include(CMakePackageConfigHelpers) | |||
configure_package_config_file( | |||
"cmake/${PROJECT_NAME}Config.cmake.in" | |||
"${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake" | |||
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" | |||
) | |||
write_basic_package_version_file( | |||
"${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake" | |||
COMPATIBILITY SameMajorVersion | |||
) | |||
install( | |||
EXPORT leveldbTargets | |||
NAMESPACE leveldb:: | |||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" | |||
) | |||
install( | |||
FILES | |||
"${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake" | |||
"${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake" | |||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" | |||
) | |||
endif(LEVELDB_INSTALL) | |||
add_executable(db_test2 | |||
"${PROJECT_SOURCE_DIR}/test/db_test2.cc" | |||
) | |||
target_link_libraries(db_test2 PRIVATE leveldb) | |||
add_executable(ttl_test | |||
"${PROJECT_SOURCE_DIR}/test/ttl_test.cc" | |||
) | |||
target_link_libraries(ttl_test PRIVATE leveldb gtest) | |||
add_executable(field_test | |||
"${PROJECT_SOURCE_DIR}/test/field_test.cc" | |||
) | |||
target_link_libraries(field_test PRIVATE leveldb gtest) | |||
# add_executable(kv_seperate_test | |||
# "${PROJECT_SOURCE_DIR}/test/kv_seperate_test.cc" | |||
# ) | |||
# target_link_libraries(kv_seperate_test PRIVATE leveldb gtest) |
@ -1,26 +1,63 @@ | |||
#include "blob_file.h" | |||
#include <fstream> | |||
namespace leveldb { | |||
BlobFile::BlobFile(const std::string& filename) : filename_(filename) { | |||
// 初始化 BlobFile,例如打开文件 | |||
} | |||
BlobFile::~BlobFile() { | |||
// 关闭文件 | |||
} | |||
Status BlobFile::Put(const Slice& key, const Slice& value) { | |||
std::ofstream file(filename_, std::ios::app | std::ios::binary); | |||
if (!file.is_open()) { | |||
return Status::IOError("Failed to open blob file"); | |||
} | |||
// 简单实现,将 key 和 value 写入文件 | |||
file.write(key.data(), key.size()); | |||
file.write(value.data(), value.size()); | |||
file.close(); | |||
return Status::OK(); | |||
} | |||
} // namespace leveldb | |||
#include "table/blob_file.h" | |||
#include "util/coding.h" | |||
#include "util/crc32c.h" | |||
#include <cassert> | |||
namespace leveldb { | |||
namespace blob { | |||
BlobFile::BlobFile(WritableFile* dest) : dest_(dest), head_(0) {} | |||
BlobFile::BlobFile(WritableFile* dest, uint64_t dest_length) | |||
: dest_(dest), head_(dest_length) {} | |||
BlobFile::~BlobFile() = default; | |||
Status BlobFile::AddRecord(const Slice& key, const Slice& value, uint64_t& offset) { | |||
// 动态写入记录,返回写入的偏移量 | |||
return EmitDynamicRecord(key, value, offset); | |||
} | |||
Status BlobFile::EmitDynamicRecord(const Slice& key, const Slice& value, uint64_t& offset) { | |||
// 记录头部,包括 key 和 value 的长度 | |||
char header[8]; // 4 字节 key 长度 + 4 字节 value 长度 | |||
uint32_t key_size = static_cast<uint32_t>(key.size()); | |||
uint32_t value_size = static_cast<uint32_t>(value.size()); | |||
// 编码 key 和 value 长度 | |||
EncodeFixed32(header, key_size); | |||
EncodeFixed32(header + 4, value_size); | |||
// 写入头部 | |||
Status s = dest_->Append(Slice(header, sizeof(header))); | |||
if (!s.ok()) { | |||
return s; | |||
} | |||
// 写入 key 和 value 数据 | |||
s = dest_->Append(key); | |||
if (!s.ok()) { | |||
return s; | |||
} | |||
s = dest_->Append(value); | |||
if (!s.ok()) { | |||
return s; | |||
} | |||
// 刷新文件到磁盘 | |||
s = dest_->Flush(); | |||
if (!s.ok()) { | |||
return s; | |||
} | |||
// 更新偏移量 | |||
offset = head_; | |||
head_ += sizeof(header) + key_size + value_size; | |||
return Status::OK(); | |||
} | |||
} // namespace blob | |||
} // namespace leveldb |
@ -1,25 +1,34 @@ | |||
#ifndef LEVELDB_BLOB_FILE_H_ | |||
#define LEVELDB_BLOB_FILE_H_ | |||
#include <string> | |||
#include "leveldb/status.h" | |||
#include "leveldb/slice.h" | |||
namespace leveldb { | |||
class BlobFile { | |||
public: | |||
BlobFile(const std::string& filename); | |||
~BlobFile(); | |||
// 写入键值对 | |||
Status Put(const Slice& key, const Slice& value); | |||
private: | |||
std::string filename_; | |||
// 内部实现,例如文件指针或缓冲区 | |||
}; | |||
} // namespace leveldb | |||
#endif // LEVELDB_BLOB_FILE_H_ | |||
#ifndef LEVELDB_BLOB_FILE_H_ | |||
#define LEVELDB_BLOB_FILE_H_ | |||
#include <string> | |||
#include "leveldb/status.h" | |||
#include "leveldb/slice.h" | |||
#include "leveldb/env.h" | |||
namespace leveldb { | |||
namespace blob { | |||
class BlobFile { | |||
public: | |||
explicit BlobFile(WritableFile* dest); | |||
BlobFile(WritableFile* dest, uint64_t dest_length); | |||
~BlobFile(); | |||
// 添加一条记录,记录写入的偏移量 | |||
Status AddRecord(const Slice& key, const Slice& value, uint64_t& offset); | |||
private: | |||
WritableFile* dest_; // 用于写入数据的目标文件 | |||
uint64_t head_; // 当前写入位置的偏移量 | |||
uint64_t bfid_; // 用于标识 BlobFile 的唯一 ID | |||
// uint64_t head_; // 当前写入文件的偏移量 | |||
Status EmitDynamicRecord(const Slice& key, const Slice& value, uint64_t& offset); | |||
}; | |||
} // namespace blob | |||
} // namespace leveldb | |||
#endif // LEVELDB_BLOB_FILE_H_ |
@ -0,0 +1,226 @@ | |||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved. | |||
// Use of this source code is governed by a BSD-style license that can be | |||
// found in the LICENSE file. See the AUTHORS file for names of contributors. | |||
#ifndef STORAGE_LEVELDB_DB_DB_IMPL_H_ | |||
#define STORAGE_LEVELDB_DB_DB_IMPL_H_ | |||
#include <atomic> | |||
#include <deque> | |||
#include <set> | |||
#include <string> | |||
#include "db/dbformat.h" | |||
#include "db/log_writer.h" | |||
#include "db/snapshot.h" | |||
#include "leveldb/db.h" | |||
#include "leveldb/env.h" | |||
#include "port/port.h" | |||
#include "port/thread_annotations.h" | |||
namespace leveldb { | |||
class MemTable; | |||
class TableCache; | |||
class Version; | |||
class VersionEdit; | |||
class VersionSet; | |||
class DBImpl : public DB { | |||
public: | |||
DBImpl(const Options& options, const std::string& dbname); | |||
DBImpl(const DBImpl&) = delete; | |||
DBImpl& operator=(const DBImpl&) = delete; | |||
~DBImpl() override; | |||
// Implementations of the DB interface | |||
Status Put(const WriteOptions&, const Slice& key, | |||
const Slice& value) override; | |||
Status Put(const WriteOptions&, const Slice& key, | |||
const Slice& value, uint64_t ttl) override; //实现新的put接口,心 | |||
Status Delete(const WriteOptions&, const Slice& key) override; | |||
Status Write(const WriteOptions& options, WriteBatch* updates) override; | |||
Status Get(const ReadOptions& options, const Slice& key, | |||
std::string* value) override; | |||
Iterator* NewIterator(const ReadOptions&) override; | |||
const Snapshot* GetSnapshot() override; | |||
void ReleaseSnapshot(const Snapshot* snapshot) override; | |||
bool GetProperty(const Slice& property, std::string* value) override; | |||
void GetApproximateSizes(const Range* range, int n, uint64_t* sizes) override; | |||
void CompactRange(const Slice* begin, const Slice* end) override; | |||
// 朴,添加是否kv分离接口,12.07 | |||
bool static key_value_separated_; | |||
// Extra methods (for testing) that are not in the public DB interface | |||
// Compact any files in the named level that overlap [*begin,*end] | |||
void TEST_CompactRange(int level, const Slice* begin, const Slice* end); | |||
// Force current memtable contents to be compacted. | |||
Status TEST_CompactMemTable(); | |||
// Return an internal iterator over the current state of the database. | |||
// The keys of this iterator are internal keys (see format.h). | |||
// The returned iterator should be deleted when no longer needed. | |||
Iterator* TEST_NewInternalIterator(); | |||
// Return the maximum overlapping data (in bytes) at next level for any | |||
// file at a level >= 1. | |||
int64_t TEST_MaxNextLevelOverlappingBytes(); | |||
// Record a sample of bytes read at the specified internal key. | |||
// Samples are taken approximately once every config::kReadBytesPeriod | |||
// bytes. | |||
void RecordReadSample(Slice key); | |||
private: | |||
friend class DB; | |||
struct CompactionState; | |||
struct Writer; | |||
// Information for a manual compaction | |||
struct ManualCompaction { | |||
int level; | |||
bool done; | |||
const InternalKey* begin; // null means beginning of key range | |||
const InternalKey* end; // null means end of key range | |||
InternalKey tmp_storage; // Used to keep track of compaction progress | |||
}; | |||
// Per level compaction stats. stats_[level] stores the stats for | |||
// compactions that produced data for the specified "level". | |||
struct CompactionStats { | |||
CompactionStats() : micros(0), bytes_read(0), bytes_written(0) {} | |||
void Add(const CompactionStats& c) { | |||
this->micros += c.micros; | |||
this->bytes_read += c.bytes_read; | |||
this->bytes_written += c.bytes_written; | |||
} | |||
int64_t micros; | |||
int64_t bytes_read; | |||
int64_t bytes_written; | |||
}; | |||
Iterator* NewInternalIterator(const ReadOptions&, | |||
SequenceNumber* latest_snapshot, | |||
uint32_t* seed); | |||
Status NewDB(); | |||
// Recover the descriptor from persistent storage. May do a significant | |||
// amount of work to recover recently logged updates. Any changes to | |||
// be made to the descriptor are added to *edit. | |||
Status Recover(VersionEdit* edit, bool* save_manifest) | |||
EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
void MaybeIgnoreError(Status* s) const; | |||
// Delete any unneeded files and stale in-memory entries. | |||
void RemoveObsoleteFiles() EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
// Compact the in-memory write buffer to disk. Switches to a new | |||
// log-file/memtable and writes a new descriptor iff successful. | |||
// Errors are recorded in bg_error_. | |||
void CompactMemTable() EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
Status RecoverLogFile(uint64_t log_number, bool last_log, bool* save_manifest, | |||
VersionEdit* edit, SequenceNumber* max_sequence) | |||
EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
Status WriteLevel0Table(MemTable* mem, VersionEdit* edit, Version* base) | |||
EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
Status MakeRoomForWrite(bool force /* compact even if there is room? */) | |||
EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
WriteBatch* BuildBatchGroup(Writer** last_writer) | |||
EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
void RecordBackgroundError(const Status& s); | |||
void MaybeScheduleCompaction() EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
static void BGWork(void* db); | |||
void BackgroundCall(); | |||
void BackgroundCompaction() EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
void CleanupCompaction(CompactionState* compact) | |||
EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
Status DoCompactionWork(CompactionState* compact) | |||
EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
Status OpenCompactionOutputFile(CompactionState* compact); | |||
Status FinishCompactionOutputFile(CompactionState* compact, Iterator* input); | |||
Status InstallCompactionResults(CompactionState* compact) | |||
EXCLUSIVE_LOCKS_REQUIRED(mutex_); | |||
const Comparator* user_comparator() const { | |||
return internal_comparator_.user_comparator(); | |||
} | |||
// Constant after construction | |||
Env* const env_; | |||
const InternalKeyComparator internal_comparator_; | |||
const InternalFilterPolicy internal_filter_policy_; | |||
const Options options_; // options_.comparator == &internal_comparator_ | |||
const bool owns_info_log_; | |||
const bool owns_cache_; | |||
const std::string dbname_; | |||
// table_cache_ provides its own synchronization | |||
TableCache* const table_cache_; | |||
// Lock over the persistent DB state. Non-null iff successfully acquired. | |||
FileLock* db_lock_; | |||
// State below is protected by mutex_ | |||
port::Mutex mutex_; | |||
std::atomic<bool> shutting_down_; | |||
port::CondVar background_work_finished_signal_ GUARDED_BY(mutex_); | |||
MemTable* mem_; | |||
MemTable* imm_ GUARDED_BY(mutex_); // Memtable being compacted | |||
std::atomic<bool> has_imm_; // So bg thread can detect non-null imm_ | |||
WritableFile* logfile_; | |||
uint64_t logfile_number_ GUARDED_BY(mutex_); | |||
log::Writer* log_; | |||
uint32_t seed_ GUARDED_BY(mutex_); // For sampling. | |||
// Queue of writers. | |||
std::deque<Writer*> writers_ GUARDED_BY(mutex_); | |||
WriteBatch* tmp_batch_ GUARDED_BY(mutex_); | |||
SnapshotList snapshots_ GUARDED_BY(mutex_); | |||
// Set of table files to protect from deletion because they are | |||
// part of ongoing compactions. | |||
std::set<uint64_t> pending_outputs_ GUARDED_BY(mutex_); | |||
// Has a background compaction been scheduled or is running? | |||
bool background_compaction_scheduled_ GUARDED_BY(mutex_); | |||
ManualCompaction* manual_compaction_ GUARDED_BY(mutex_); | |||
VersionSet* const versions_ GUARDED_BY(mutex_); | |||
// Have we encountered a background error in paranoid mode? | |||
Status bg_error_ GUARDED_BY(mutex_); | |||
CompactionStats stats_[config::kNumLevels] GUARDED_BY(mutex_); | |||
}; | |||
// Sanitize db options. The caller should delete result.info_log if | |||
// it is not equal to src.info_log. | |||
Options SanitizeOptions(const std::string& db, | |||
const InternalKeyComparator* icmp, | |||
const InternalFilterPolicy* ipolicy, | |||
const Options& src); | |||
} // namespace leveldb | |||
#endif // STORAGE_LEVELDB_DB_DB_IMPL_H_ |
@ -0,0 +1,119 @@ | |||
#include "gtest/gtest.h" | |||
#include "leveldb/env.h" | |||
#include "leveldb/db.h" | |||
#include "table/blob_file.h" // 假设 BlobFile 的头文件 | |||
using namespace leveldb; | |||
constexpr int value_size = 2048; // 单个值的大小 | |||
constexpr int data_size = 128 << 20; // 总数据大小 | |||
constexpr int min_blob_size = 1024; // KV 分离的阈值 | |||
Status OpenDB(std::string dbName, DB** db) { | |||
Options options; | |||
options.create_if_missing = true; | |||
options.key_value_separated = true; // 启用 KV 分离 | |||
return DB::Open(options, dbName, db); | |||
} | |||
// 插入数据,模拟 KV 分离 | |||
void InsertData(DB* db) { | |||
WriteOptions writeOptions; | |||
int key_num = data_size / value_size; | |||
srand(static_cast<unsigned int>(time(0))); | |||
for (int i = 0; i < key_num; i++) { | |||
int key_ = rand() % key_num + 1; | |||
std::string key = std::to_string(key_); | |||
std::string value(value_size, 'a'); // 大 value | |||
db->Put(writeOptions, key, value); // 使用标准 Put 接口插入 | |||
} | |||
} | |||
// 检查数据是否被正确存入 BlobFile | |||
void VerifyBlobFile(const std::string& blob_file_path, int expected_entries) { | |||
BlobFile blobfile(blob_file_path, BlobFile::kReadMode); | |||
Status status = blobfile.Open(); | |||
ASSERT_TRUE(status.ok()); | |||
int entry_count = 0; | |||
BlobFile::Iterator it = blobfile.NewIterator(); | |||
for (it.SeekToFirst(); it.Valid(); it.Next()) { | |||
++entry_count; | |||
const Slice& key = it.key(); | |||
const Slice& value = it.value(); | |||
ASSERT_GT(value.size(), min_blob_size); // 确认 value 大于阈值 | |||
} | |||
ASSERT_EQ(entry_count, expected_entries); // 确认条目数是否正确 | |||
blobfile.Close(); | |||
} | |||
// KV 分离读写测试 | |||
TEST(TestKVSeparation, WriteAndRead) { | |||
DB* db; | |||
if (OpenDB("testdb", &db).ok() == false) { | |||
std::cerr << "open db failed" << std::endl; | |||
abort(); | |||
} | |||
// 插入数据 | |||
InsertData(db); | |||
// 验证 BlobFile 内容 | |||
VerifyBlobFile("blob_data", data_size / value_size); | |||
// 随机点查数据 | |||
ReadOptions readOptions; | |||
srand(static_cast<unsigned int>(time(0))); | |||
int key_num = data_size / value_size; | |||
for (int i = 0; i < 100; i++) { | |||
int key_ = rand() % key_num + 1; | |||
std::string key = std::to_string(key_); | |||
std::string value; | |||
Status status = db->Get(readOptions, key, &value); | |||
ASSERT_TRUE(status.ok()); // 验证是否成功读取 | |||
if (value.size() > min_blob_size) { | |||
ASSERT_TRUE(value == std::string(value_size, 'a')); // 验证大 value 的内容 | |||
} | |||
} | |||
delete db; | |||
} | |||
// KV 分离压缩测试 | |||
TEST(TestKVSeparation, Compaction) { | |||
DB* db; | |||
if (OpenDB("testdb", &db).ok() == false) { | |||
std::cerr << "open db failed" << std::endl; | |||
abort(); | |||
} | |||
// 插入数据 | |||
InsertData(db); | |||
leveldb::Range ranges[1]; | |||
ranges[0] = leveldb::Range("-", "A"); | |||
uint64_t sizes[1]; | |||
db->GetApproximateSizes(ranges, 1, sizes); | |||
ASSERT_GT(sizes[0], 0); | |||
// 执行压缩 | |||
db->CompactRange(nullptr, nullptr); | |||
// 验证压缩后主数据区的大小 | |||
ranges[0] = leveldb::Range("-", "A"); | |||
db->GetApproximateSizes(ranges, 1, sizes); | |||
ASSERT_EQ(sizes[0], 0); | |||
// 验证 BlobFile 内容仍然有效 | |||
VerifyBlobFile("blob_data", data_size / value_size); | |||
delete db; | |||
} | |||
int main(int argc, char** argv) { | |||
testing::InitGoogleTest(&argc, argv); | |||
return RUN_ALL_TESTS(); | |||
} |
@ -1,323 +1,345 @@ | |||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved. | |||
// Use of this source code is governed by a BSD-style license that can be | |||
// found in the LICENSE file. See the AUTHORS file for names of contributors. | |||
#include "leveldb/table_builder.h" | |||
#include <cassert> | |||
#include "leveldb/comparator.h" | |||
#include "leveldb/env.h" | |||
#include "leveldb/filter_policy.h" | |||
#include "leveldb/options.h" | |||
#include "table/block_builder.h" | |||
#include "table/filter_block.h" | |||
#include "table/format.h" | |||
#include "util/coding.h" | |||
#include "util/crc32c.h" | |||
#include "db/db_impl.h" //朴 | |||
#include "table/blob_file.h" //朴 | |||
#include "table/block.h" //朴 | |||
const size_t min_blob_size = 1024; // 设定值大小阈值为 1KB,朴 | |||
namespace leveldb { | |||
BlobFile* blobfile = new BlobFile("blob_data"); // 初始化全局 blobfile 对象,朴 | |||
class BlobFileManager { | |||
public: | |||
static BlobFile* GetInstance() { | |||
static BlobFile instance("blob_data"); | |||
return &instance; | |||
} | |||
}; | |||
struct TableBuilder::Rep { | |||
Rep(const Options& opt, WritableFile* f) | |||
: options(opt), | |||
index_block_options(opt), | |||
file(f), | |||
offset(0), | |||
data_block(&options), | |||
index_block(&index_block_options), | |||
num_entries(0), | |||
closed(false), | |||
filter_block(opt.filter_policy == nullptr | |||
? nullptr | |||
: new FilterBlockBuilder(opt.filter_policy)), | |||
pending_index_entry(false) { | |||
index_block_options.block_restart_interval = 1; | |||
} | |||
Options options; | |||
Options index_block_options; | |||
WritableFile* file; | |||
uint64_t offset; | |||
Status status; | |||
BlockBuilder data_block; | |||
BlockBuilder index_block; | |||
std::string last_key; | |||
int64_t num_entries; | |||
bool closed; // Either Finish() or Abandon() has been called. | |||
FilterBlockBuilder* filter_block; | |||
// We do not emit the index entry for a block until we have seen the | |||
// first key for the next data block. This allows us to use shorter | |||
// keys in the index block. For example, consider a block boundary | |||
// between the keys "the quick brown fox" and "the who". We can use | |||
// "the r" as the key for the index block entry since it is >= all | |||
// entries in the first block and < all entries in subsequent | |||
// blocks. | |||
// | |||
// Invariant: r->pending_index_entry is true only if data_block is empty. | |||
bool pending_index_entry; | |||
BlockHandle pending_handle; // Handle to add to index block | |||
std::string compressed_output; | |||
}; | |||
TableBuilder::TableBuilder(const Options& options, WritableFile* file) | |||
: rep_(new Rep(options, file)) { | |||
if (rep_->filter_block != nullptr) { | |||
rep_->filter_block->StartBlock(0); | |||
} | |||
} | |||
TableBuilder::~TableBuilder() { | |||
assert(rep_->closed); // Catch errors where caller forgot to call Finish() | |||
delete rep_->filter_block; | |||
delete rep_; | |||
} | |||
Status TableBuilder::ChangeOptions(const Options& options) { | |||
// Note: if more fields are added to Options, update | |||
// this function to catch changes that should not be allowed to | |||
// change in the middle of building a Table. | |||
if (options.comparator != rep_->options.comparator) { | |||
return Status::InvalidArgument("changing comparator while building table"); | |||
} | |||
// Note that any live BlockBuilders point to rep_->options and therefore | |||
// will automatically pick up the updated options. | |||
rep_->options = options; | |||
rep_->index_block_options = options; | |||
rep_->index_block_options.block_restart_interval = 1; | |||
return Status::OK(); | |||
} | |||
void TableBuilder::Add(const Slice& key, const Slice& value) { | |||
Rep* r = rep_; | |||
assert(!r->closed); | |||
if (!ok()) return; | |||
if (r->num_entries > 0) { | |||
assert(r->options.comparator->Compare(key, Slice(r->last_key)) > 0); | |||
} | |||
if (r->pending_index_entry) { | |||
assert(r->data_block.empty()); | |||
r->options.comparator->FindShortestSeparator(&r->last_key, key); | |||
std::string handle_encoding; | |||
r->pending_handle.EncodeTo(&handle_encoding); | |||
r->index_block.Add(r->last_key, Slice(handle_encoding)); | |||
r->pending_index_entry = false; | |||
} | |||
if (r->filter_block != nullptr) { | |||
r->filter_block->AddKey(key); | |||
} | |||
r->last_key.assign(key.data(), key.size()); | |||
r->num_entries++; | |||
r->data_block.Add(key, value); | |||
const size_t estimated_block_size = r->data_block.CurrentSizeEstimate(); | |||
if (estimated_block_size >= r->options.block_size) { | |||
Flush(); | |||
} | |||
} | |||
void TableBuilder::Flush() { | |||
Rep* r = rep_; | |||
assert(!r->closed); | |||
if (!ok()) return; | |||
if (r->data_block.empty()) return; //朴,正常判断 | |||
assert(!r->pending_index_entry); | |||
if (DBImpl::key_value_separated_) { | |||
// 这里获取数据块内容并初始化 Block 对象,朴 | |||
Slice block_content = r->data_block.Finish(); | |||
BlockContents contents; | |||
contents.data = block_content; | |||
contents.heap_allocated = false; | |||
contents.cachable = false; | |||
// 初始化 Block | |||
Block data_block(contents); | |||
std::unique_ptr<Iterator> iter(data_block.NewIterator(Options().comparator)); | |||
// 遍历数据块中的键值对 | |||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { | |||
const Slice& key = iter->key(); | |||
const Slice& value = iter->value(); | |||
// 检查值是否大于阈值 | |||
if (value.size() > min_blob_size) { | |||
// 将值存储到 blobfile 中 | |||
Status status = blobfile->Put(key, value); | |||
if (!status.ok()) { | |||
r->status = status; | |||
} | |||
} | |||
} | |||
} | |||
WriteBlock(&r->data_block, &r->pending_handle); //将数据块写入文件,并获取数据块的句柄。 | |||
if (ok()) { | |||
r->pending_index_entry = true; | |||
r->status = r->file->Flush(); //刷新 | |||
} | |||
if (r->filter_block != nullptr) { | |||
r->filter_block->StartBlock(r->offset); | |||
} | |||
} | |||
void TableBuilder::WriteBlock(BlockBuilder* block, BlockHandle* handle) { | |||
// File format contains a sequence of blocks where each block has: | |||
// block_data: uint8[n] | |||
// type: uint8 | |||
// crc: uint32 | |||
assert(ok()); | |||
Rep* r = rep_; | |||
Slice raw = block->Finish(); | |||
Slice block_contents; | |||
CompressionType type = r->options.compression; | |||
// TODO(postrelease): Support more compression options: zlib? | |||
switch (type) { | |||
case kNoCompression: | |||
block_contents = raw; | |||
break; | |||
case kSnappyCompression: { | |||
std::string* compressed = &r->compressed_output; | |||
if (port::Snappy_Compress(raw.data(), raw.size(), compressed) && | |||
compressed->size() < raw.size() - (raw.size() / 8u)) { | |||
block_contents = *compressed; | |||
} else { | |||
// Snappy not supported, or compressed less than 12.5%, so just | |||
// store uncompressed form | |||
block_contents = raw; | |||
type = kNoCompression; | |||
} | |||
break; | |||
} | |||
case kZstdCompression: { | |||
std::string* compressed = &r->compressed_output; | |||
if (port::Zstd_Compress(r->options.zstd_compression_level, raw.data(), | |||
raw.size(), compressed) && | |||
compressed->size() < raw.size() - (raw.size() / 8u)) { | |||
block_contents = *compressed; | |||
} else { | |||
// Zstd not supported, or compressed less than 12.5%, so just | |||
// store uncompressed form | |||
block_contents = raw; | |||
type = kNoCompression; | |||
} | |||
break; | |||
} | |||
} | |||
WriteRawBlock(block_contents, type, handle); | |||
r->compressed_output.clear(); | |||
block->Reset(); | |||
} | |||
void TableBuilder::WriteRawBlock(const Slice& block_contents, | |||
CompressionType type, BlockHandle* handle) { | |||
Rep* r = rep_; | |||
handle->set_offset(r->offset); | |||
handle->set_size(block_contents.size()); | |||
r->status = r->file->Append(block_contents); | |||
if (r->status.ok()) { | |||
char trailer[kBlockTrailerSize]; | |||
trailer[0] = type; | |||
uint32_t crc = crc32c::Value(block_contents.data(), block_contents.size()); | |||
crc = crc32c::Extend(crc, trailer, 1); // Extend crc to cover block type | |||
EncodeFixed32(trailer + 1, crc32c::Mask(crc)); | |||
r->status = r->file->Append(Slice(trailer, kBlockTrailerSize)); | |||
if (r->status.ok()) { | |||
r->offset += block_contents.size() + kBlockTrailerSize; | |||
} | |||
} | |||
} | |||
Status TableBuilder::status() const { return rep_->status; } | |||
Status TableBuilder::Finish() { | |||
Rep* r = rep_; | |||
Flush(); | |||
assert(!r->closed); | |||
r->closed = true; | |||
BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle; | |||
// Write filter block | |||
if (ok() && r->filter_block != nullptr) { | |||
WriteRawBlock(r->filter_block->Finish(), kNoCompression, | |||
&filter_block_handle); | |||
} | |||
// Write metaindex block | |||
if (ok()) { | |||
BlockBuilder meta_index_block(&r->options); | |||
if (r->filter_block != nullptr) { | |||
// Add mapping from "filter.Name" to location of filter data | |||
std::string key = "filter."; | |||
key.append(r->options.filter_policy->Name()); | |||
std::string handle_encoding; | |||
filter_block_handle.EncodeTo(&handle_encoding); | |||
meta_index_block.Add(key, handle_encoding); | |||
} | |||
// TODO(postrelease): Add stats and other meta blocks | |||
WriteBlock(&meta_index_block, &metaindex_block_handle); | |||
} | |||
// Write index block | |||
if (ok()) { | |||
if (r->pending_index_entry) { | |||
r->options.comparator->FindShortSuccessor(&r->last_key); | |||
std::string handle_encoding; | |||
r->pending_handle.EncodeTo(&handle_encoding); | |||
r->index_block.Add(r->last_key, Slice(handle_encoding)); | |||
r->pending_index_entry = false; | |||
} | |||
WriteBlock(&r->index_block, &index_block_handle); | |||
} | |||
// Write footer | |||
if (ok()) { | |||
Footer footer; | |||
footer.set_metaindex_handle(metaindex_block_handle); | |||
footer.set_index_handle(index_block_handle); | |||
std::string footer_encoding; | |||
footer.EncodeTo(&footer_encoding); | |||
r->status = r->file->Append(footer_encoding); | |||
if (r->status.ok()) { | |||
r->offset += footer_encoding.size(); | |||
} | |||
} | |||
return r->status; | |||
} | |||
void TableBuilder::Abandon() { | |||
Rep* r = rep_; | |||
assert(!r->closed); | |||
r->closed = true; | |||
} | |||
uint64_t TableBuilder::NumEntries() const { return rep_->num_entries; } | |||
uint64_t TableBuilder::FileSize() const { return rep_->offset; } | |||
} // namespace leveldb | |||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved. | |||
// Use of this source code is governed by a BSD-style license that can be | |||
// found in the LICENSE file. See the AUTHORS file for names of contributors. | |||
#include "leveldb/table_builder.h" | |||
#include <cassert> | |||
#include "leveldb/comparator.h" | |||
#include "leveldb/env.h" | |||
#include "leveldb/filter_policy.h" | |||
#include "leveldb/options.h" | |||
#include "table/block_builder.h" | |||
#include "table/filter_block.h" | |||
#include "table/format.h" | |||
#include "util/coding.h" | |||
#include "util/crc32c.h" | |||
#include "db/db_impl.h" //朴 | |||
#include "table/blob_file.h" //朴 | |||
#include "table/block.h" //朴 | |||
const size_t min_blob_size = 1024; // 设定值大小阈值为 1KB,朴 | |||
namespace leveldb { | |||
struct TableBuilder::Rep { | |||
Rep(const Options& opt, WritableFile* f) | |||
: options(opt), | |||
index_block_options(opt), | |||
file(f), | |||
offset(0), | |||
data_block(&options), | |||
index_block(&index_block_options), | |||
num_entries(0), | |||
closed(false), | |||
filter_block(opt.filter_policy == nullptr | |||
? nullptr | |||
: new FilterBlockBuilder(opt.filter_policy)), | |||
pending_index_entry(false) { | |||
index_block_options.block_restart_interval = 1; | |||
} | |||
Options options; | |||
Options index_block_options; | |||
WritableFile* file; | |||
uint64_t offset; | |||
Status status; | |||
BlockBuilder data_block; | |||
BlockBuilder index_block; | |||
std::string last_key; | |||
int64_t num_entries; | |||
bool closed; // Either Finish() or Abandon() has been called. | |||
FilterBlockBuilder* filter_block; | |||
// We do not emit the index entry for a block until we have seen the | |||
// first key for the next data block. This allows us to use shorter | |||
// keys in the index block. For example, consider a block boundary | |||
// between the keys "the quick brown fox" and "the who". We can use | |||
// "the r" as the key for the index block entry since it is >= all | |||
// entries in the first block and < all entries in subsequent | |||
// blocks. | |||
// | |||
// Invariant: r->pending_index_entry is true only if data_block is empty. | |||
bool pending_index_entry; | |||
BlockHandle pending_handle; // Handle to add to index block | |||
std::string compressed_output; | |||
}; | |||
TableBuilder::TableBuilder(const Options& options, WritableFile* file) | |||
: rep_(new Rep(options, file)) { | |||
if (rep_->filter_block != nullptr) { | |||
rep_->filter_block->StartBlock(0); | |||
} | |||
} | |||
TableBuilder::~TableBuilder() { | |||
assert(rep_->closed); // Catch errors where caller forgot to call Finish() | |||
delete rep_->filter_block; | |||
delete rep_; | |||
} | |||
Status TableBuilder::ChangeOptions(const Options& options) { | |||
// Note: if more fields are added to Options, update | |||
// this function to catch changes that should not be allowed to | |||
// change in the middle of building a Table. | |||
if (options.comparator != rep_->options.comparator) { | |||
return Status::InvalidArgument("changing comparator while building table"); | |||
} | |||
// Note that any live BlockBuilders point to rep_->options and therefore | |||
// will automatically pick up the updated options. | |||
rep_->options = options; | |||
rep_->index_block_options = options; | |||
rep_->index_block_options.block_restart_interval = 1; | |||
return Status::OK(); | |||
} | |||
void TableBuilder::Add(const Slice& key, const Slice& value) { | |||
Rep* r = rep_; | |||
assert(!r->closed); | |||
if (!ok()) return; | |||
if (r->num_entries > 0) { | |||
assert(r->options.comparator->Compare(key, Slice(r->last_key)) > 0); | |||
} | |||
if (r->pending_index_entry) { | |||
assert(r->data_block.empty()); | |||
r->options.comparator->FindShortestSeparator(&r->last_key, key); | |||
std::string handle_encoding; | |||
r->pending_handle.EncodeTo(&handle_encoding); | |||
r->index_block.Add(r->last_key, Slice(handle_encoding)); | |||
r->pending_index_entry = false; | |||
} | |||
if (r->filter_block != nullptr) { | |||
r->filter_block->AddKey(key); | |||
} | |||
r->last_key.assign(key.data(), key.size()); | |||
r->num_entries++; | |||
r->data_block.Add(key, value); | |||
const size_t estimated_block_size = r->data_block.CurrentSizeEstimate(); | |||
if (estimated_block_size >= r->options.block_size) { | |||
Flush(); | |||
} | |||
} | |||
void TableBuilder::Flush() { | |||
Rep* r = rep_; | |||
assert(!r->closed); | |||
if (!ok()) return; | |||
if (r->data_block.empty()) return; //朴,正常判断 | |||
assert(!r->pending_index_entry); | |||
if (DBImpl::key_value_separated_) { | |||
// 这里获取数据块内容并初始化 Block 对象,朴 | |||
Slice block_content = r->data_block.Finish(); | |||
BlockContents contents; | |||
contents.data = block_content; | |||
contents.heap_allocated = false; | |||
contents.cachable = false; | |||
Rep* new_rep = new Rep(r->options, r->file); // 创建一个新的 Rep 实例 | |||
new_rep->offset = r->offset; // 新的 offset 初始化为当前的 offset | |||
new_rep->num_entries = r->num_entries; | |||
// 初始化 Block | |||
Block data_block(contents); | |||
leveldb::WritableFile* dest = nullptr; | |||
leveldb::blob::BlobFile blobfile(dest); // 可以动态生成文件名以防止重复 // 初始化 BlobFile 对象,朴 | |||
leveldb::WritableFile* file; | |||
int bfid = DBImpl::NewBlobNum(); // 生成唯一的 blobfile id | |||
std::unique_ptr<Iterator> iter(data_block.NewIterator(Options().comparator)); | |||
// 遍历数据块中的键值对 | |||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { | |||
const Slice& key = iter->key(); | |||
const Slice& value = iter->value(); | |||
// 检查值是否大于阈值 | |||
if (value.size() > min_blob_size) { | |||
// 将值存储到 blobfile 中 | |||
uint64_t offset; // 局部变量存储偏移量 | |||
Status status = blobfile.AddRecord(key, value, offset); | |||
if (!status.ok()) { | |||
r->status = status; | |||
} | |||
// 这里修改 value,存储 Blob 的 offset 和 bfid | |||
std::string new_value = EncodeBlobValue(offset, bfid); | |||
new_rep->data_block.Add(key, Slice(new_value)); | |||
} | |||
else{ | |||
// 不需要 Blob 存储,直接处理普通值 | |||
new_rep->data_block.Add(key, value); | |||
} | |||
} | |||
} | |||
WriteBlock(&r->data_block, &r->pending_handle); //将数据块写入文件,并获取数据块的句柄。 | |||
if (ok()) { | |||
r->pending_index_entry = true; | |||
r->status = r->file->Flush(); //刷新 | |||
} | |||
if (r->filter_block != nullptr) { | |||
r->filter_block->StartBlock(r->offset); | |||
} | |||
} | |||
std::string TableBuilder::EncodeBlobValue(uint64_t offset, int bfid) { | |||
// 自定义方法:编码新的 Blob 值 | |||
std::string result; | |||
// 为 result 分配空间 | |||
result.resize(8 + 4); // 64位 (8字节) + 32位 (4字节) | |||
// 将 offset 和 bfid 编码成一个新的值 | |||
std::string result; | |||
EncodeFixed64(&result[0], offset); // 编码 offset | |||
EncodeFixed32(&result[8], bfid); // 编码 bfid | |||
return result; | |||
} | |||
void TableBuilder::WriteBlock(BlockBuilder* block, BlockHandle* handle) { | |||
// File format contains a sequence of blocks where each block has: | |||
// block_data: uint8[n] | |||
// type: uint8 | |||
// crc: uint32 | |||
assert(ok()); | |||
Rep* r = rep_; | |||
Slice raw = block->Finish(); | |||
Slice block_contents; | |||
CompressionType type = r->options.compression; | |||
// TODO(postrelease): Support more compression options: zlib? | |||
switch (type) { | |||
case kNoCompression: | |||
block_contents = raw; | |||
break; | |||
case kSnappyCompression: { | |||
std::string* compressed = &r->compressed_output; | |||
if (port::Snappy_Compress(raw.data(), raw.size(), compressed) && | |||
compressed->size() < raw.size() - (raw.size() / 8u)) { | |||
block_contents = *compressed; | |||
} else { | |||
// Snappy not supported, or compressed less than 12.5%, so just | |||
// store uncompressed form | |||
block_contents = raw; | |||
type = kNoCompression; | |||
} | |||
break; | |||
} | |||
case kZstdCompression: { | |||
std::string* compressed = &r->compressed_output; | |||
if (port::Zstd_Compress(r->options.zstd_compression_level, raw.data(), | |||
raw.size(), compressed) && | |||
compressed->size() < raw.size() - (raw.size() / 8u)) { | |||
block_contents = *compressed; | |||
} else { | |||
// Zstd not supported, or compressed less than 12.5%, so just | |||
// store uncompressed form | |||
block_contents = raw; | |||
type = kNoCompression; | |||
} | |||
break; | |||
} | |||
} | |||
WriteRawBlock(block_contents, type, handle); | |||
r->compressed_output.clear(); | |||
block->Reset(); | |||
} | |||
void TableBuilder::WriteRawBlock(const Slice& block_contents, | |||
CompressionType type, BlockHandle* handle) { | |||
Rep* r = rep_; | |||
handle->set_offset(r->offset); | |||
handle->set_size(block_contents.size()); | |||
r->status = r->file->Append(block_contents); | |||
if (r->status.ok()) { | |||
char trailer[kBlockTrailerSize]; | |||
trailer[0] = type; | |||
uint32_t crc = crc32c::Value(block_contents.data(), block_contents.size()); | |||
crc = crc32c::Extend(crc, trailer, 1); // Extend crc to cover block type | |||
EncodeFixed32(trailer + 1, crc32c::Mask(crc)); | |||
r->status = r->file->Append(Slice(trailer, kBlockTrailerSize)); | |||
if (r->status.ok()) { | |||
r->offset += block_contents.size() + kBlockTrailerSize; | |||
} | |||
} | |||
} | |||
Status TableBuilder::status() const { return rep_->status; } | |||
Status TableBuilder::Finish() { | |||
Rep* r = rep_; | |||
Flush(); | |||
assert(!r->closed); | |||
r->closed = true; | |||
BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle; | |||
// Write filter block | |||
if (ok() && r->filter_block != nullptr) { | |||
WriteRawBlock(r->filter_block->Finish(), kNoCompression, | |||
&filter_block_handle); | |||
} | |||
// Write metaindex block | |||
if (ok()) { | |||
BlockBuilder meta_index_block(&r->options); | |||
if (r->filter_block != nullptr) { | |||
// Add mapping from "filter.Name" to location of filter data | |||
std::string key = "filter."; | |||
key.append(r->options.filter_policy->Name()); | |||
std::string handle_encoding; | |||
filter_block_handle.EncodeTo(&handle_encoding); | |||
meta_index_block.Add(key, handle_encoding); | |||
} | |||
// TODO(postrelease): Add stats and other meta blocks | |||
WriteBlock(&meta_index_block, &metaindex_block_handle); | |||
} | |||
// Write index block | |||
if (ok()) { | |||
if (r->pending_index_entry) { | |||
r->options.comparator->FindShortSuccessor(&r->last_key); | |||
std::string handle_encoding; | |||
r->pending_handle.EncodeTo(&handle_encoding); | |||
r->index_block.Add(r->last_key, Slice(handle_encoding)); | |||
r->pending_index_entry = false; | |||
} | |||
WriteBlock(&r->index_block, &index_block_handle); | |||
} | |||
// Write footer | |||
if (ok()) { | |||
Footer footer; | |||
footer.set_metaindex_handle(metaindex_block_handle); | |||
footer.set_index_handle(index_block_handle); | |||
std::string footer_encoding; | |||
footer.EncodeTo(&footer_encoding); | |||
r->status = r->file->Append(footer_encoding); | |||
if (r->status.ok()) { | |||
r->offset += footer_encoding.size(); | |||
} | |||
} | |||
return r->status; | |||
} | |||
void TableBuilder::Abandon() { | |||
Rep* r = rep_; | |||
assert(!r->closed); | |||
r->closed = true; | |||
} | |||
uint64_t TableBuilder::NumEntries() const { return rep_->num_entries; } | |||
uint64_t TableBuilder::FileSize() const { return rep_->offset; } | |||
} // namespace leveldb |