36#include <gtest/gtest.h>
51#if defined(__unix__) || defined(__APPLE__)
60 namespace fs = std::filesystem;
64 static std::atomic<unsigned long long>
counter{0};
65 const auto now = std::chrono::steady_clock::now().time_since_epoch().count();
66 const auto id = std::to_string(
now) +
"_" + std::to_string(
counter++);
67 const fs::path dir = fs::temp_directory_path() /
"aleph_file_bplustree_tests";
68 fs::create_directories(dir);
69 return dir / (
id +
".idx");
76 explicit TempFile(fs::path p) : path(
std::move(p)) {}
82 fs::remove(path.string() +
".wal",
ec);
83 fs::remove(path.string() +
".wal.tmp",
ec);
84 fs::remove(path.string() +
".journal",
ec);
85 fs::remove(path.string() +
".journal.tmp",
ec);
86 fs::remove(path.string() +
".lock",
ec);
87 fs::remove(path.string() +
".old",
ec);
88 fs::remove(path.string() +
".new",
ec);
97 for (
size_t i = 0; i < arr.
size(); ++i)
98 ret.push_back(arr[i]);
102#if defined(__unix__) || defined(__APPLE__)
105# if defined(__SANITIZE_THREAD__)
107# elif defined(__has_feature)
108# if __has_feature(thread_sanitizer)
118 template <
typename F>
165 const std::uint64_t
size,
166 const std::uint64_t root_page,
167 const std::uint64_t first_leaf_page,
168 const std::uint64_t page_count,
169 const std::uint64_t free_page_head,
170 const std::uint64_t checkpoint_sequence,
192 const size_t page_bytes,
196 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
200 crc,
page_blob.data() + (page_id - 1) * page_bytes, page_bytes);
215 constexpr size_t page_bytes =
216 sizeof(std::uint8_t) +
sizeof(std::uint16_t) * 2 + 3
217 +
sizeof(std::uint64_t) +
sizeof(std::uint64_t) +
sizeof(
int) *
max_keys
218 +
sizeof(std::uint64_t) *
max_children +
sizeof(std::uint32_t);
223 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
224 in.read(magic.data(), magic.size());
227 auto read_u32 = [&](std::uint32_t & value)
229 in.read(
reinterpret_cast<char *
>(&value),
sizeof(value));
232 auto read_u64 = [&](std::uint64_t & value)
234 in.read(
reinterpret_cast<char *
>(&value),
sizeof(value));
237 auto read_u8 = [&](std::uint8_t & value)
239 in.read(
reinterpret_cast<char *
>(&value),
sizeof(value));
243 std::uint32_t version = 0;
262 std::uint64_t
size = 0;
263 std::uint64_t root_page = 0;
264 std::uint64_t first_leaf_page = 0;
265 std::uint64_t page_count = 0;
266 std::uint64_t free_page_head = 0;
267 std::uint64_t checkpoint_sequence = 0;
277 std::vector<char>
page_blob(page_bytes * page_count);
281 std::ofstream
out(
wal_path, std::ios::binary | std::ios::trunc);
284 const auto wal_magic =
288 out.write(wal_magic.data(), wal_magic.size());
290 out.write(
reinterpret_cast<const char *
>(&key_size),
sizeof(key_size));
291 out.write(
reinterpret_cast<const char *
>(&min_degree),
sizeof(min_degree));
294 out.write(
reinterpret_cast<const char *
>(&
size),
sizeof(
size));
295 out.write(
reinterpret_cast<const char *
>(&root_page),
sizeof(root_page));
296 out.write(
reinterpret_cast<const char *
>(&first_leaf_page),
sizeof(first_leaf_page));
297 out.write(
reinterpret_cast<const char *
>(&page_count),
sizeof(page_count));
298 out.write(
reinterpret_cast<const char *
>(&free_page_head),
sizeof(free_page_head));
299 out.write(
reinterpret_cast<const char *
>(&checkpoint_sequence),
sizeof(checkpoint_sequence));
300 out.write(
reinterpret_cast<const char *
>(&page_count),
sizeof(page_count));
303 size, root_page, first_leaf_page, page_count, free_page_head,
304 checkpoint_sequence, page_count);
307 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
309 out.write(
reinterpret_cast<const char *
>(&page_id),
sizeof(page_id));
310 out.write(
page_blob.data() + (page_id - 1) * page_bytes, page_bytes);
316 out.write(
reinterpret_cast<const char *
>(&checkpoint_sequence),
317 sizeof(checkpoint_sequence));
318 out.write(
reinterpret_cast<const char *
>(&page_count),
sizeof(page_count));
333 +
sizeof(std::uint32_t) * 2 +
sizeof(std::uint64_t)
334 +
sizeof(std::uint8_t) + 7 +
sizeof(std::uint64_t) * 3);
336 std::uint64_t page_count = 0;
337 in.read(
reinterpret_cast<char *
>(&page_count),
sizeof(page_count));
344 constexpr size_t page_bytes =
345 sizeof(std::uint8_t) +
sizeof(std::uint16_t) * 2 + 3
346 +
sizeof(std::uint64_t) +
sizeof(std::uint64_t) +
sizeof(
int) * 5
347 +
sizeof(std::uint64_t) * 6 +
sizeof(std::uint32_t);
351 std::vector<std::uint64_t>
page_ids;
355 auto read_u32 = [&](std::uint32_t & value)
357 in.read(
reinterpret_cast<char *
>(&value),
sizeof(value));
360 auto read_u64 = [&](std::uint64_t & value)
362 in.read(
reinterpret_cast<char *
>(&value),
sizeof(value));
365 auto read_u8 = [&](std::uint8_t & value)
367 in.read(
reinterpret_cast<char *
>(&value),
sizeof(value));
371 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
373 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size>
376 std::uint32_t key_size = 0;
377 std::uint64_t min_degree = 0;
383 in.read(magic.data(), magic.size());
403 std::uint64_t page_id = 0;
406 in.seekg(
static_cast<std::streamoff
>(page_bytes), std::ios::cur);
416 const std::streamoff
offset,
const size_t size)
418 std::ifstream
in(src, std::ios::binary);
420 std::fstream
out(
dst, std::ios::binary | std::ios::in | std::ios::out);
423 std::vector<char> buffer(
size);
426 in.read(buffer.data(), buffer.size());
431 out.write(buffer.data(), buffer.size());
442 for (
int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
447 (std::vector<int>{120, 125, 130, 135, 140}));
453 (std::vector<int>{105, 110, 115, 120, 125, 130, 135, 140, 145, 150}));
455 (std::vector<int>{120, 125, 130, 135, 140}));
465 for (
int value : {10, 20, 30, 40, 50})
472 (std::vector<int>{10, 30, 40, 50, 60}));
476 (std::vector<int>{10, 20, 30, 40, 50}));
485 for (
int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
492 (std::vector<int>{105, 110, 115, 120, 125, 130, 135, 140, 145, 150}));
494 std::vector<int>
band;
504 EXPECT_EQ(
band, (std::vector<int>{120, 125, 130, 135, 140}));
511 const std::vector<std::string>
expected = {
512 "alfa",
"beta",
"delta",
"epsilon",
"gama"
517 tree(
tmp.path.string(),
false);
525 (std::vector<std::string>{
"beta",
"delta",
"epsilon"}));
536 std::vector<std::string>
band;
537 for (
auto it =
reopened.get_range_it(std::string(
"beta"),
538 std::string(
"epsilon"));
539 it.has_curr(); it.next())
540 band.push_back(it.get_curr());
541 EXPECT_EQ(
band, (std::vector<std::string>{
"beta",
"delta",
"epsilon"}));
550 tree(
tmp.path.string(),
false);
560 std::uint64_t first = 0;
561 std::uint64_t second = 0;
596 for (
int value : {10, 20, 30, 40})
648 std::ofstream
out(
tmp.path.string() +
".wal",
649 std::ios::binary | std::ios::trunc);
651 out <<
"pending recovery";
662#if defined(__unix__) || defined(__APPLE__)
664 GTEST_SKIP() <<
"TSAN does not support these fork-based lock checks";
684 return child.
range(0, 30).
size() == 2 ? 0 : 2;
703 catch (
const std::runtime_error &)
716 GTEST_SKIP() <<
"fork-based lock validation is only available on Unix-like systems";
725 std::ofstream
out(
tmp.path.string() +
".lock",
726 std::ios::binary | std::ios::trunc);
761 for (
int value : {5, 10, 15, 20, 25, 30})
765 std::fstream
io(
tmp.path, std::ios::binary | std::ios::in | std::ios::out);
767 io.seekg(0, std::ios::end);
768 const auto end =
io.tellg();
770 io.seekg(end - std::streamoff(1));
775 byte ^=
static_cast<char>(0x33);
776 io.seekp(end - std::streamoff(1));
788 const auto journal =
tmp.path.string() +
".journal";
792 for (
int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
798 fs::copy_file(
tmp.path,
journal, fs::copy_options::overwrite_existing,
ec);
802 std::ofstream
out(
tmp.path, std::ios::binary | std::ios::trunc);
810 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45}));
817 const auto wal =
tmp.path.string() +
".wal";
821 for (
int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
828 std::ofstream
out(
tmp.path, std::ios::binary | std::ios::trunc);
836 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45}));
843 const auto wal =
tmp.path.string() +
".wal";
847 for (
int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
853 std::fstream
wal_io(
wal, std::ios::binary | std::ios::in | std::ios::out);
855 wal_io.seekg(0, std::ios::end);
863 byte ^=
static_cast<char>(0x11);
870 std::ofstream
out(
tmp.path, std::ios::binary | std::ios::trunc);
881 constexpr size_t header_bytes =
883 +
sizeof(std::uint64_t) * 6 +
sizeof(std::uint8_t) + 7;
884 constexpr size_t page_bytes =
885 sizeof(std::uint8_t) +
sizeof(std::uint16_t) * 2 + 3
886 +
sizeof(std::uint64_t) +
sizeof(std::uint64_t) +
sizeof(
int) * 5
887 +
sizeof(std::uint64_t) * 6 +
sizeof(std::uint32_t);
890 const auto wal =
tmp.path.string() +
".wal";
896 for (
int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
923 static_cast<std::streamoff
>(header_bytes
937 const auto wal =
tmp.path.string() +
".wal";
942 for (
int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
964 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}));
974 for (
int value = 1; value <= 80; ++value)
977 for (
int value : {1, 2, 3, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 80})
989 (std::vector<int>{10, 11, 12, 13, 14, 18, 19, 20}));
Simple dynamic array with automatic resizing and functional operations.
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
bool has_curr() const noexcept
Return whether the iterator still points to a key.
Persistent page-managed B+ Tree stored in a single binary file.
bool insert(const Key &key)
Insert a key if it is not already present.
Iterator get_it() const noexcept
Return a lazy iterator over the full key order.
std::uint64_t checkpoint_sequence() const noexcept
Return the durable checkpoint sequence stored in the backing file.
void sync() const
Flush the current tree image using redo WAL or full-image fallback.
bool verify() const
Verify structural B+ Tree invariants across cached pages.
bool remove(const Key &key)
Remove a key if present.
void reload()
Discard unsynchronized changes and reload pages from disk.
Array< Key > range(const Key &first, const Key &last) const
Collect all keys in the inclusive range [first, last].
void checkpoint() const
Synonym for sync().
Iterator get_range_it(const Key &first, const Key &last) const
Return a lazy iterator over an inclusive key range.
Persistent page-managed B-Tree stored in a single binary file.
bool insert(const Key &key)
Insert a key if it is not already present.
const long double offset[]
Offset values indexed by symbol string length (bounded by MAX_OFFSET_INDEX)
std::uint32_t crc32_add_bytes(std::uint32_t crc, const void *data, const size_t size) noexcept
constexpr std::array< char, Ordered_Tree_Snapshot_Magic_Size > ordered_tree_snapshot_magic(const char(&text)[N]) noexcept
std::uint32_t crc32_begin() noexcept
std::uint32_t crc32_add(std::uint32_t crc, const T &value) noexcept
constexpr size_t Ordered_Tree_Snapshot_Magic_Size
std::uint32_t crc32_finish(const std::uint32_t crc) noexcept
Main namespace for Aleph-w library functions.
size_t size(Node *root) noexcept
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
std::string code(Node *root)
Compute a string with the Lukasiewicz`s word of a tree.
std::vector< typename C::Item_Type > to_vector(const C &c)
Convert a container to a std::vector.
Page-managed persistent B-Tree with file-backed storage.
Page-managed persistent B+ Tree with file-backed storage.