36#include <gtest/gtest.h>
51#if defined(__unix__) || defined(__APPLE__)
62 namespace fs = std::filesystem;
68 return static_cast<long long>(
_getpid());
70 return static_cast<long long>(
getpid());
84 static std::atomic<unsigned long long>
counter{0};
85 const auto now = std::chrono::steady_clock::now().time_since_epoch().count();
86 const auto id = std::to_string(
now) +
"_" + std::to_string(process_id()) +
87 "_" + std::to_string(
counter++);
88 const fs::path dir = fs::temp_directory_path() /
"aleph_file_bplustree_tests";
89 fs::create_directories(dir);
90 return dir / (
id +
".idx");
97 explicit TempFile(fs::path p) : path(
std::move(p)) {}
102 fs::remove(path,
ec);
103 fs::remove(path.string() +
".wal",
ec);
104 fs::remove(path.string() +
".wal.tmp",
ec);
105 fs::remove(path.string() +
".journal",
ec);
106 fs::remove(path.string() +
".journal.tmp",
ec);
107 fs::remove(path.string() +
".lock",
ec);
108 fs::remove(path.string() +
".old",
ec);
109 fs::remove(path.string() +
".new",
ec);
113 template <
typename T>
118 for (
size_t i = 0; i < arr.
size(); ++i)
119 ret.push_back(arr[i]);
123#if defined(__unix__) || defined(__APPLE__)
126# if defined(__SANITIZE_THREAD__)
128# elif defined(__has_feature)
129# if __has_feature(thread_sanitizer)
139 template <
typename F>
186 const std::uint64_t
size,
187 const std::uint64_t root_page,
188 const std::uint64_t first_leaf_page,
189 const std::uint64_t page_count,
190 const std::uint64_t free_page_head,
191 const std::uint64_t checkpoint_sequence,
213 const size_t page_bytes,
217 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
221 crc,
page_blob.data() + (page_id - 1) * page_bytes, page_bytes);
236 constexpr size_t page_bytes =
237 sizeof(std::uint8_t) +
sizeof(std::uint16_t) * 2 + 3
238 +
sizeof(std::uint64_t) +
sizeof(std::uint64_t) +
sizeof(
int) *
max_keys
239 +
sizeof(std::uint64_t) *
max_children +
sizeof(std::uint32_t);
244 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
245 in.read(magic.data(), magic.size());
250 in.read(
reinterpret_cast<char *
>(&
value),
sizeof(
value));
255 in.read(
reinterpret_cast<char *
>(&
value),
sizeof(
value));
260 in.read(
reinterpret_cast<char *
>(&
value),
sizeof(
value));
264 std::uint32_t version = 0;
283 std::uint64_t
size = 0;
284 std::uint64_t root_page = 0;
285 std::uint64_t first_leaf_page = 0;
286 std::uint64_t page_count = 0;
287 std::uint64_t free_page_head = 0;
288 std::uint64_t checkpoint_sequence = 0;
298 std::vector<char>
page_blob(page_bytes * page_count);
302 std::ofstream
out(
wal_path, std::ios::binary | std::ios::trunc);
305 const auto wal_magic =
309 out.write(wal_magic.data(), wal_magic.size());
311 out.write(
reinterpret_cast<const char *
>(&key_size),
sizeof(key_size));
312 out.write(
reinterpret_cast<const char *
>(&min_degree),
sizeof(min_degree));
315 out.write(
reinterpret_cast<const char *
>(&
size),
sizeof(
size));
316 out.write(
reinterpret_cast<const char *
>(&root_page),
sizeof(root_page));
317 out.write(
reinterpret_cast<const char *
>(&first_leaf_page),
sizeof(first_leaf_page));
318 out.write(
reinterpret_cast<const char *
>(&page_count),
sizeof(page_count));
319 out.write(
reinterpret_cast<const char *
>(&free_page_head),
sizeof(free_page_head));
320 out.write(
reinterpret_cast<const char *
>(&checkpoint_sequence),
sizeof(checkpoint_sequence));
321 out.write(
reinterpret_cast<const char *
>(&page_count),
sizeof(page_count));
324 size, root_page, first_leaf_page, page_count, free_page_head,
325 checkpoint_sequence, page_count);
328 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
330 out.write(
reinterpret_cast<const char *
>(&page_id),
sizeof(page_id));
331 out.write(
page_blob.data() + (page_id - 1) * page_bytes, page_bytes);
337 out.write(
reinterpret_cast<const char *
>(&checkpoint_sequence),
338 sizeof(checkpoint_sequence));
339 out.write(
reinterpret_cast<const char *
>(&page_count),
sizeof(page_count));
354 +
sizeof(std::uint32_t) * 2 +
sizeof(std::uint64_t)
355 +
sizeof(std::uint8_t) + 7 +
sizeof(std::uint64_t) * 3);
357 std::uint64_t page_count = 0;
358 in.read(
reinterpret_cast<char *
>(&page_count),
sizeof(page_count));
365 constexpr size_t page_bytes =
366 sizeof(std::uint8_t) +
sizeof(std::uint16_t) * 2 + 3
367 +
sizeof(std::uint64_t) +
sizeof(std::uint64_t) +
sizeof(
int) * 5
368 +
sizeof(std::uint64_t) * 6 +
sizeof(std::uint32_t);
372 std::vector<std::uint64_t>
page_ids;
378 in.read(
reinterpret_cast<char *
>(&
value),
sizeof(
value));
383 in.read(
reinterpret_cast<char *
>(&
value),
sizeof(
value));
388 in.read(
reinterpret_cast<char *
>(&
value),
sizeof(
value));
392 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
394 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size>
397 std::uint32_t key_size = 0;
398 std::uint64_t min_degree = 0;
404 in.read(magic.data(), magic.size());
424 std::uint64_t page_id = 0;
427 in.seekg(
static_cast<std::streamoff
>(page_bytes), std::ios::cur);
437 const std::streamoff
offset,
const size_t size)
439 std::ifstream
in(src, std::ios::binary);
441 std::fstream
out(
dst, std::ios::binary | std::ios::in | std::ios::out);
444 std::vector<char> buffer(
size);
447 in.read(buffer.data(), buffer.size());
452 out.write(buffer.data(), buffer.size());
463 for (
int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
468 (std::vector<int>{120, 125, 130, 135, 140}));
474 (std::vector<int>{105, 110, 115, 120, 125, 130, 135, 140, 145, 150}));
476 (std::vector<int>{120, 125, 130, 135, 140}));
486 for (
int value : {10, 20, 30, 40, 50})
493 (std::vector<int>{10, 30, 40, 50, 60}));
497 (std::vector<int>{10, 20, 30, 40, 50}));
506 for (
int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
513 (std::vector<int>{105, 110, 115, 120, 125, 130, 135, 140, 145, 150}));
515 std::vector<int>
band;
525 EXPECT_EQ(
band, (std::vector<int>{120, 125, 130, 135, 140}));
532 const std::vector<std::string>
expected = {
533 "alfa",
"beta",
"delta",
"epsilon",
"gama"
538 tree(
tmp.path.string(),
false);
546 (std::vector<std::string>{
"beta",
"delta",
"epsilon"}));
557 std::vector<std::string>
band;
558 for (
auto it =
reopened.get_range_it(std::string(
"beta"),
559 std::string(
"epsilon"));
560 it.has_curr(); it.next())
561 band.push_back(it.get_curr());
562 EXPECT_EQ(
band, (std::vector<std::string>{
"beta",
"delta",
"epsilon"}));
571 tree(
tmp.path.string(),
false);
581 std::uint64_t first = 0;
582 std::uint64_t second = 0;
617 for (
int value : {10, 20, 30, 40})
669 std::ofstream
out(
tmp.path.string() +
".wal",
670 std::ios::binary | std::ios::trunc);
672 out <<
"pending recovery";
683#if defined(__unix__) || defined(__APPLE__)
685 GTEST_SKIP() <<
"TSAN does not support these fork-based lock checks";
705 return child.
range(0, 30).
size() == 2 ? 0 : 2;
724 catch (
const std::runtime_error &)
737 GTEST_SKIP() <<
"fork-based lock validation is only available on Unix-like systems";
746 std::ofstream
out(
tmp.path.string() +
".lock",
747 std::ios::binary | std::ios::trunc);
782 for (
int value : {5, 10, 15, 20, 25, 30})
786 std::fstream
io(
tmp.path, std::ios::binary | std::ios::in | std::ios::out);
788 io.seekg(0, std::ios::end);
789 const auto end =
io.tellg();
791 io.seekg(end - std::streamoff(1));
796 byte ^=
static_cast<char>(0x33);
797 io.seekp(end - std::streamoff(1));
809 const auto journal =
tmp.path.string() +
".journal";
813 for (
int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
819 fs::copy_file(
tmp.path,
journal, fs::copy_options::overwrite_existing,
ec);
823 std::ofstream
out(
tmp.path, std::ios::binary | std::ios::trunc);
831 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45}));
838 const auto wal =
tmp.path.string() +
".wal";
842 for (
int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
849 std::ofstream
out(
tmp.path, std::ios::binary | std::ios::trunc);
857 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45}));
864 const auto wal =
tmp.path.string() +
".wal";
868 for (
int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
874 std::fstream
wal_io(
wal, std::ios::binary | std::ios::in | std::ios::out);
876 wal_io.seekg(0, std::ios::end);
884 byte ^=
static_cast<char>(0x11);
891 std::ofstream
out(
tmp.path, std::ios::binary | std::ios::trunc);
902 constexpr size_t header_bytes =
904 +
sizeof(std::uint64_t) * 6 +
sizeof(std::uint8_t) + 7;
905 constexpr size_t page_bytes =
906 sizeof(std::uint8_t) +
sizeof(std::uint16_t) * 2 + 3
907 +
sizeof(std::uint64_t) +
sizeof(std::uint64_t) +
sizeof(
int) * 5
908 +
sizeof(std::uint64_t) * 6 +
sizeof(std::uint32_t);
911 const auto wal =
tmp.path.string() +
".wal";
917 for (
int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
944 static_cast<std::streamoff
>(header_bytes
958 const auto wal =
tmp.path.string() +
".wal";
963 for (
int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
985 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}));
998 for (
int value : {1, 2, 3, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 80})
1010 (std::vector<int>{10, 11, 12, 13, 14, 18, 19, 20}));
size_t size_t int32_t value
size_t size_t int32_t * out
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.
Minimal std::expected-style result type for C++20.
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
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
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.