Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
file_bplus_tree_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
36#include <gtest/gtest.h>
37
38#include <array>
39#include <atomic>
40#include <bit>
41#include <chrono>
42#include <cstdint>
43#include <filesystem>
44#include <fstream>
45#include <string>
46#include <vector>
47
48#include <tpl_file_b_tree.H>
49#include <tpl_file_bplus_tree.H>
50
51#if defined(__unix__) || defined(__APPLE__)
52# include <sys/wait.h>
53# include <unistd.h>
54#endif
55
56using namespace Aleph;
57
58namespace
59{
60 namespace fs = std::filesystem;
61
62 fs::path make_temp_path()
63 {
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");
70 }
71
72 struct TempFile
73 {
74 fs::path path;
75
76 explicit TempFile(fs::path p) : path(std::move(p)) {}
77
78 ~TempFile()
79 {
80 std::error_code ec;
81 fs::remove(path, ec);
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);
89 }
90 };
91
92 template <typename T>
93 std::vector<T> to_vector(const Array<T> & arr)
94 {
95 std::vector<T> ret;
96 ret.reserve(arr.size());
97 for (size_t i = 0; i < arr.size(); ++i)
98 ret.push_back(arr[i]);
99 return ret;
100 }
101
102#if defined(__unix__) || defined(__APPLE__)
103 constexpr bool running_under_tsan() noexcept
104 {
105# if defined(__SANITIZE_THREAD__)
106 return true;
107# elif defined(__has_feature)
108# if __has_feature(thread_sanitizer)
109 return true;
110# else
111 return false;
112# endif
113# else
114 return false;
115# endif
116 }
117
118 template <typename F>
119 int run_in_child(F && fn)
120 {
121 const pid_t pid = ::fork();
122 if (pid < 0)
123 return -1;
124
125 if (pid == 0)
126 {
127 int code = 100;
128 try
129 {
130 code = fn();
131 }
132 catch (...)
133 {
134 code = 101;
135 }
136 ::_exit(code);
137 }
138
139 int status = 0;
140 if (::waitpid(pid, &status, 0) < 0)
141 return -1;
142 return status;
143 }
144#endif
145
146 constexpr std::uint32_t file_bplus_tree_test_file_version = 5;
147 constexpr std::uint32_t file_bplus_tree_test_wal_version = 4;
148 constexpr std::uint32_t file_bplus_tree_test_key_size =
150 constexpr std::uint64_t file_bplus_tree_test_min_degree = 3;
151 constexpr std::uint8_t file_bplus_tree_test_encoding = 3;
152
153 std::array<char, 7> file_bplus_tree_reserved_bytes()
154 {
155 std::array<char, 7> reserved = {};
157 reserved[0] = static_cast<char>(codec_id & 0xFFu);
158 reserved[1] = static_cast<char>((codec_id >> 8) & 0xFFu);
159 reserved[2] = static_cast<char>((codec_id >> 16) & 0xFFu);
160 reserved[3] = static_cast<char>((codec_id >> 24) & 0xFFu);
161 return reserved;
162 }
163
164 std::uint32_t bplus_wal_checksum(const std::uint32_t wal_version,
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,
171 const std::uint64_t dirty_count)
172 {
173 std::uint32_t crc = Aleph::detail::crc32_begin();
175
182 crc = Aleph::detail::crc32_add(crc, root_page);
183 crc = Aleph::detail::crc32_add(crc, first_leaf_page);
184 crc = Aleph::detail::crc32_add(crc, page_count);
185 crc = Aleph::detail::crc32_add(crc, free_page_head);
186 crc = Aleph::detail::crc32_add(crc, checkpoint_sequence);
189 }
190
191 std::uint32_t bplus_wal_payload_checksum(const std::uint64_t page_count,
192 const size_t page_bytes,
193 const std::vector<char> & page_blob)
194 {
195 std::uint32_t crc = Aleph::detail::crc32_begin();
196 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
197 {
198 crc = Aleph::detail::crc32_add(crc, page_id);
200 crc, page_blob.data() + (page_id - 1) * page_bytes, page_bytes);
201 }
203 }
204
205 void write_bplus_wal_from_file(const fs::path & data_path,
206 const fs::path & wal_path)
207 {
208 constexpr std::uint32_t file_version = file_bplus_tree_test_file_version;
209 constexpr std::uint32_t wal_version = file_bplus_tree_test_wal_version;
210 constexpr std::uint32_t key_size = file_bplus_tree_test_key_size;
211 constexpr std::uint64_t min_degree = file_bplus_tree_test_min_degree;
212 constexpr std::uint8_t encoding = file_bplus_tree_test_encoding;
213 constexpr size_t max_keys = 5;
214 constexpr size_t max_children = 6;
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);
219
220 std::ifstream in(data_path, std::ios::binary);
221 ASSERT_TRUE(in.good());
222
223 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
224 in.read(magic.data(), magic.size());
225 ASSERT_TRUE(in.good());
226
227 auto read_u32 = [&](std::uint32_t & value)
228 {
229 in.read(reinterpret_cast<char *>(&value), sizeof(value));
230 ASSERT_TRUE(in.good());
231 };
232 auto read_u64 = [&](std::uint64_t & value)
233 {
234 in.read(reinterpret_cast<char *>(&value), sizeof(value));
235 ASSERT_TRUE(in.good());
236 };
237 auto read_u8 = [&](std::uint8_t & value)
238 {
239 in.read(reinterpret_cast<char *>(&value), sizeof(value));
240 ASSERT_TRUE(in.good());
241 };
242
243 std::uint32_t version = 0;
244 std::uint32_t file_key_size = 0;
245 std::uint64_t file_min_degree = 0;
246 std::uint8_t file_encoding = 0;
247 read_u32(version);
251 ASSERT_EQ(version, file_version);
252 ASSERT_EQ(file_key_size, key_size);
253 ASSERT_EQ(file_min_degree, min_degree);
255
257 std::array<char, 7> stored_reserved = {};
258 in.read(stored_reserved.data(), stored_reserved.size());
259 ASSERT_TRUE(in.good());
261
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;
268 std::uint32_t checksum = 0;
269 read_u64(size);
270 read_u64(root_page);
271 read_u64(first_leaf_page);
272 read_u64(page_count);
273 read_u64(free_page_head);
274 read_u64(checkpoint_sequence);
276
277 std::vector<char> page_blob(page_bytes * page_count);
278 in.read(page_blob.data(), page_blob.size());
279 ASSERT_TRUE(in.good());
280
281 std::ofstream out(wal_path, std::ios::binary | std::ios::trunc);
282 ASSERT_TRUE(out.good());
283
284 const auto wal_magic =
286 const auto trailer_magic =
288 out.write(wal_magic.data(), wal_magic.size());
289 out.write(reinterpret_cast<const char *>(&wal_version), sizeof(wal_version));
290 out.write(reinterpret_cast<const char *>(&key_size), sizeof(key_size));
291 out.write(reinterpret_cast<const char *>(&min_degree), sizeof(min_degree));
292 out.write(reinterpret_cast<const char *>(&encoding), sizeof(encoding));
293 out.write(reserved.data(), reserved.size());
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));
301 const auto wal_checksum = bplus_wal_checksum(
303 size, root_page, first_leaf_page, page_count, free_page_head,
304 checkpoint_sequence, page_count);
305 out.write(reinterpret_cast<const char *>(&wal_checksum), sizeof(wal_checksum));
306
307 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
308 {
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);
311 }
312
313 const auto payload_checksum =
314 bplus_wal_payload_checksum(page_count, page_bytes, page_blob);
315 out.write(trailer_magic.data(), trailer_magic.size());
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));
319 out.write(reinterpret_cast<const char *>(&payload_checksum),
320 sizeof(payload_checksum));
321
322 ASSERT_TRUE(out.good());
323 }
324
325 std::uint64_t read_bplus_page_count(const fs::path & data_path)
326 {
327 std::ifstream in(data_path, std::ios::binary);
328 EXPECT_TRUE(in.good());
329 if (not in.good())
330 return 0;
331
333 + sizeof(std::uint32_t) * 2 + sizeof(std::uint64_t)
334 + sizeof(std::uint8_t) + 7 + sizeof(std::uint64_t) * 3);
335 EXPECT_TRUE(in.good());
336 std::uint64_t page_count = 0;
337 in.read(reinterpret_cast<char *>(&page_count), sizeof(page_count));
338 EXPECT_TRUE(in.good());
339 return page_count;
340 }
341
342 std::vector<std::uint64_t> read_bplus_wal_page_ids(const fs::path & wal_path)
343 {
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);
348
349 std::ifstream in(wal_path, std::ios::binary);
350 EXPECT_TRUE(in.good());
351 std::vector<std::uint64_t> page_ids;
352 if (not in.good())
353 return page_ids;
354
355 auto read_u32 = [&](std::uint32_t & value)
356 {
357 in.read(reinterpret_cast<char *>(&value), sizeof(value));
358 EXPECT_TRUE(in.good());
359 };
360 auto read_u64 = [&](std::uint64_t & value)
361 {
362 in.read(reinterpret_cast<char *>(&value), sizeof(value));
363 EXPECT_TRUE(in.good());
364 };
365 auto read_u8 = [&](std::uint8_t & value)
366 {
367 in.read(reinterpret_cast<char *>(&value), sizeof(value));
368 EXPECT_TRUE(in.good());
369 };
370
371 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
372 std::array<char, 7> reserved = {};
373 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size>
374 trailer_magic = {};
375 std::uint32_t wal_version = 0;
376 std::uint32_t key_size = 0;
377 std::uint64_t min_degree = 0;
378 std::uint8_t encoding = 0;
379 std::uint64_t ignored64 = 0;
380 std::uint32_t ignored32 = 0;
381 std::uint64_t dirty_count = 0;
382
383 in.read(magic.data(), magic.size());
384 EXPECT_TRUE(in.good());
386 read_u32(key_size);
387 read_u64(min_degree);
389 in.read(reserved.data(), reserved.size());
390 EXPECT_TRUE(in.good());
391 read_u64(ignored64); // size
392 read_u64(ignored64); // root page
393 read_u64(ignored64); // first leaf
394 read_u64(ignored64); // page count
395 read_u64(ignored64); // free page head
396 read_u64(ignored64); // checkpoint sequence
398 read_u32(ignored32); // header checksum
399
400 page_ids.reserve(dirty_count);
401 for (std::uint64_t i = 0; i < dirty_count; ++i)
402 {
403 std::uint64_t page_id = 0;
404 read_u64(page_id);
405 page_ids.push_back(page_id);
406 in.seekg(static_cast<std::streamoff>(page_bytes), std::ios::cur);
407 EXPECT_TRUE(in.good());
408 }
409
410 in.read(trailer_magic.data(), trailer_magic.size());
411 EXPECT_TRUE(in.good());
412 return page_ids;
413 }
414
415 void copy_region(const fs::path & src, const fs::path & dst,
416 const std::streamoff offset, const size_t size)
417 {
418 std::ifstream in(src, std::ios::binary);
419 ASSERT_TRUE(in.good());
420 std::fstream out(dst, std::ios::binary | std::ios::in | std::ios::out);
421 ASSERT_TRUE(out.good());
422
423 std::vector<char> buffer(size);
424 in.seekg(offset);
425 ASSERT_TRUE(in.good());
426 in.read(buffer.data(), buffer.size());
427 ASSERT_TRUE(in.good());
428
429 out.seekp(offset);
430 ASSERT_TRUE(out.good());
431 out.write(buffer.data(), buffer.size());
432 ASSERT_TRUE(out.good());
433 }
434}
435
437{
438 TempFile tmp(make_temp_path());
439
440 {
441 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
442 for (int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
443 EXPECT_TRUE(tree.insert(value));
444
445 EXPECT_TRUE(tree.verify());
446 EXPECT_EQ(to_vector(tree.range(118, 142)),
447 (std::vector<int>{120, 125, 130, 135, 140}));
448 }
449
451 EXPECT_TRUE(reopened.verify());
453 (std::vector<int>{105, 110, 115, 120, 125, 130, 135, 140, 145, 150}));
454 EXPECT_EQ(to_vector(reopened.range(118, 142)),
455 (std::vector<int>{120, 125, 130, 135, 140}));
456 EXPECT_EQ(reopened.lower_bound(126), std::optional<int>(130));
457 EXPECT_EQ(reopened.upper_bound(140), std::optional<int>(145));
458}
459
461{
462 TempFile tmp(make_temp_path());
463
464 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
465 for (int value : {10, 20, 30, 40, 50})
466 EXPECT_TRUE(tree.insert(value));
467 tree.sync();
468
469 EXPECT_TRUE(tree.insert(60));
470 EXPECT_TRUE(tree.remove(20));
471 EXPECT_EQ(to_vector(tree.range(10, 70)),
472 (std::vector<int>{10, 30, 40, 50, 60}));
473
474 tree.reload();
475 EXPECT_EQ(to_vector(tree.range(10, 70)),
476 (std::vector<int>{10, 20, 30, 40, 50}));
477 EXPECT_TRUE(tree.verify());
478}
479
481{
482 TempFile tmp(make_temp_path());
483
484 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
485 for (int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
486 EXPECT_TRUE(tree.insert(value));
487
488 std::vector<int> all_keys;
489 for (auto it = tree.get_it(); it.has_curr(); it.next_ne())
490 all_keys.push_back(it.get_curr());
492 (std::vector<int>{105, 110, 115, 120, 125, 130, 135, 140, 145, 150}));
493
494 std::vector<int> band;
495 auto band_it = tree.get_range_it(118, 141);
496 ASSERT_TRUE(band_it.has_curr());
497 EXPECT_EQ(*band_it, 120);
498 EXPECT_EQ(band_it.get_curr(), 120);
499 band.push_back(band_it.get_curr());
500 band_it.next();
501 for (; band_it.has_curr(); band_it.next_ne())
502 band.push_back(band_it.get_curr());
503
504 EXPECT_EQ(band, (std::vector<int>{120, 125, 130, 135, 140}));
505}
506
508{
509 TempFile tmp(make_temp_path());
511 const std::vector<std::string> expected = {
512 "alfa", "beta", "delta", "epsilon", "gama"
513 };
514
515 {
517 tree(tmp.path.string(), false);
518 EXPECT_TRUE(tree.insert("delta"));
519 EXPECT_TRUE(tree.insert("alfa"));
520 EXPECT_TRUE(tree.insert("gama"));
521 EXPECT_TRUE(tree.insert("beta"));
522 EXPECT_TRUE(tree.insert("epsilon"));
523 EXPECT_TRUE(tree.verify());
524 EXPECT_EQ(to_vector(tree.range(std::string("beta"), std::string("epsilon"))),
525 (std::vector<std::string>{"beta", "delta", "epsilon"}));
526 tree.sync();
527 }
528
530 reopened(tmp.path.string(),
533 EXPECT_TRUE(reopened.verify());
535
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"}));
542}
543
545{
546 TempFile tmp(make_temp_path());
548
550 tree(tmp.path.string(), false);
551 EXPECT_TRUE(tree.insert("demasiado-largo"));
552 EXPECT_THROW(tree.sync(), std::runtime_error);
553 EXPECT_TRUE(tree.verify());
554}
555
557{
558 TempFile tmp(make_temp_path());
559
560 std::uint64_t first = 0;
561 std::uint64_t second = 0;
562
563 {
564 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
565 EXPECT_EQ(tree.checkpoint_sequence(), 1u);
566 EXPECT_TRUE(tree.insert(10));
567 tree.checkpoint();
568 first = tree.checkpoint_sequence();
569 EXPECT_GT(first, 1u);
570
571 EXPECT_TRUE(tree.insert(20));
572 tree.sync();
573 second = tree.checkpoint_sequence();
574 EXPECT_GT(second, first);
575 }
576
578 EXPECT_EQ(reopened.checkpoint_sequence(), second);
579}
580
582{
583 TempFile tmp(make_temp_path());
584
585 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
586 EXPECT_THROW((File_BPlus_Tree<int, Aleph::less<int>, 3>(tmp.path.string(), false)),
587 std::runtime_error);
588}
589
591{
592 TempFile tmp(make_temp_path());
593
594 {
595 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
596 for (int value : {10, 20, 30, 40})
597 EXPECT_TRUE(tree.insert(value));
598 tree.sync();
599 }
600
602 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Only);
604 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Only);
605 const auto read_only_mode =
607
608 EXPECT_TRUE(reader1.is_read_only());
609 EXPECT_EQ(reader1.open_mode(), read_only_mode);
610 EXPECT_EQ(to_vector(reader2.range(0, 50)), (std::vector<int>{10, 20, 30, 40}));
612 tmp.path.string(),
614 std::runtime_error);
615}
616
618{
619 TempFile tmp(make_temp_path());
620
621 {
622 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
623 EXPECT_TRUE(tree.insert(10));
624 tree.sync();
625 }
626
628 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Only);
629
630 EXPECT_THROW(reader.insert(20), std::runtime_error);
631 EXPECT_THROW(reader.remove(10), std::runtime_error);
632 EXPECT_THROW(reader.clear(), std::runtime_error);
633 EXPECT_THROW(reader.sync(), std::runtime_error);
634 EXPECT_EQ(to_vector(reader.range(0, 20)), (std::vector<int>{10}));
635}
636
638{
639 TempFile tmp(make_temp_path());
640
641 {
642 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
643 EXPECT_TRUE(tree.insert(10));
644 tree.sync();
645 }
646
647 {
648 std::ofstream out(tmp.path.string() + ".wal",
649 std::ios::binary | std::ios::trunc);
650 ASSERT_TRUE(out.good());
651 out << "pending recovery";
652 }
653
655 tmp.path.string(),
657 std::runtime_error);
658}
659
661{
662#if defined(__unix__) || defined(__APPLE__)
663 if (running_under_tsan())
664 GTEST_SKIP() << "TSAN does not support these fork-based lock checks";
665
666 TempFile tmp(make_temp_path());
667
668 {
669 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
670 EXPECT_TRUE(tree.insert(10));
671 EXPECT_TRUE(tree.insert(20));
672 tree.sync();
673 }
674
676 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Only);
677
678 const int shared_status = run_in_child([&]() -> int
679 {
680 try
681 {
683 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Only);
684 return child.range(0, 30).size() == 2 ? 0 : 2;
685 }
686 catch (...)
687 {
688 return 1;
689 }
690 });
694
695 const int writer_status = run_in_child([&]() -> int
696 {
697 try
698 {
700 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Write);
701 return 1;
702 }
703 catch (const std::runtime_error &)
704 {
705 return 0;
706 }
707 catch (...)
708 {
709 return 2;
710 }
711 });
715#else
716 GTEST_SKIP() << "fork-based lock validation is only available on Unix-like systems";
717#endif
718}
719
721{
722 TempFile tmp(make_temp_path());
723
724 {
725 std::ofstream out(tmp.path.string() + ".lock",
726 std::ios::binary | std::ios::trunc);
727 ASSERT_TRUE(out.good());
728 out << "stale\n";
729 }
730
731 {
732 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
733 EXPECT_TRUE(tree.insert(5));
734 tree.sync();
735 }
736
737 File_BPlus_Tree<int, Aleph::less<int>, 3> reopened(tmp.path.string(), false);
738 EXPECT_EQ(to_vector(reopened.range(0, 10)), (std::vector<int>{5}));
739}
740
742{
743 TempFile tmp(make_temp_path());
744
745 {
746 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
747 EXPECT_TRUE(tree.insert(1));
748 EXPECT_TRUE(tree.insert(2));
749 }
750
751 EXPECT_THROW((File_BPlus_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
752 std::runtime_error);
753}
754
756{
757 TempFile tmp(make_temp_path());
758
759 {
760 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
761 for (int value : {5, 10, 15, 20, 25, 30})
762 EXPECT_TRUE(tree.insert(value));
763 }
764
765 std::fstream io(tmp.path, std::ios::binary | std::ios::in | std::ios::out);
766 ASSERT_TRUE(io.good());
767 io.seekg(0, std::ios::end);
768 const auto end = io.tellg();
769 ASSERT_GT(end, 0);
770 io.seekg(end - std::streamoff(1));
771
772 char byte = 0;
773 io.read(&byte, 1);
774 ASSERT_TRUE(io.good());
775 byte ^= static_cast<char>(0x33);
776 io.seekp(end - std::streamoff(1));
777 io.write(&byte, 1);
778 ASSERT_TRUE(io.good());
779 io.close();
780
781 EXPECT_THROW((File_BPlus_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
782 std::runtime_error);
783}
784
786{
787 TempFile tmp(make_temp_path());
788 const auto journal = tmp.path.string() + ".journal";
789
790 {
791 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
792 for (int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
793 EXPECT_TRUE(tree.insert(value));
794 tree.sync();
795 }
796
797 std::error_code ec;
798 fs::copy_file(tmp.path, journal, fs::copy_options::overwrite_existing, ec);
799 ASSERT_FALSE(ec) << ec.message();
800
801 {
802 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
803 ASSERT_TRUE(out.good());
804 out << "corrupt";
805 }
806
808 EXPECT_TRUE(reopened.verify());
809 EXPECT_EQ(to_vector(reopened.range(0, 50)),
810 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45}));
811 EXPECT_FALSE(fs::exists(journal));
812}
813
815{
816 TempFile tmp(make_temp_path());
817 const auto wal = tmp.path.string() + ".wal";
818
819 {
820 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
821 for (int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
822 EXPECT_TRUE(tree.insert(value));
823 }
824
826
827 {
828 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
829 ASSERT_TRUE(out.good());
830 out << "corrupt";
831 }
832
834 EXPECT_TRUE(reopened.verify());
835 EXPECT_EQ(to_vector(reopened.range(0, 50)),
836 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45}));
837 EXPECT_FALSE(fs::exists(wal));
838}
839
841{
842 TempFile tmp(make_temp_path());
843 const auto wal = tmp.path.string() + ".wal";
844
845 {
846 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
847 for (int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
848 EXPECT_TRUE(tree.insert(value));
849 }
850
852
853 std::fstream wal_io(wal, std::ios::binary | std::ios::in | std::ios::out);
854 ASSERT_TRUE(wal_io.good());
855 wal_io.seekg(0, std::ios::end);
856 const auto wal_end = wal_io.tellg();
857 ASSERT_GT(wal_end, 0);
858 wal_io.seekg(wal_end - std::streamoff(1));
859
860 char byte = 0;
861 wal_io.read(&byte, 1);
862 ASSERT_TRUE(wal_io.good());
863 byte ^= static_cast<char>(0x11);
864 wal_io.seekp(wal_end - std::streamoff(1));
865 wal_io.write(&byte, 1);
866 ASSERT_TRUE(wal_io.good());
867 wal_io.close();
868
869 {
870 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
871 ASSERT_TRUE(out.good());
872 out << "corrupt";
873 }
874
875 EXPECT_THROW((File_BPlus_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
876 std::runtime_error);
877}
878
880{
881 constexpr size_t header_bytes =
882 Aleph::detail::Ordered_Tree_Snapshot_Magic_Size + sizeof(std::uint32_t) * 3
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);
888
889 TempFile tmp(make_temp_path());
890 const auto wal = tmp.path.string() + ".wal";
891 const auto old_snapshot = tmp.path.string() + ".old";
892 const auto new_snapshot = tmp.path.string() + ".new";
893
894 {
895 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
896 for (int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
897 EXPECT_TRUE(tree.insert(value));
898 tree.sync();
899 }
900
901 std::error_code ec;
902 fs::copy_file(tmp.path, old_snapshot, fs::copy_options::overwrite_existing, ec);
903 ASSERT_FALSE(ec) << ec.message();
904
905 {
906 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
907 EXPECT_TRUE(tree.remove(120));
908 tree.sync();
909 }
910
911 fs::copy_file(tmp.path, new_snapshot, fs::copy_options::overwrite_existing, ec);
912 ASSERT_FALSE(ec) << ec.message();
914
917 ASSERT_GE(dirty_pages.size(), 2u);
918
919 fs::copy_file(old_snapshot, tmp.path, fs::copy_options::overwrite_existing, ec);
920 ASSERT_FALSE(ec) << ec.message();
921 copy_region(new_snapshot, tmp.path, 0, header_bytes);
923 static_cast<std::streamoff>(header_bytes
924 + (dirty_pages.front() - 1) * page_bytes),
925 page_bytes);
926
929 EXPECT_TRUE(reopened.verify());
931 EXPECT_FALSE(fs::exists(wal));
932}
933
935{
936 TempFile tmp(make_temp_path());
937 const auto wal = tmp.path.string() + ".wal";
938 const auto old_snapshot = tmp.path.string() + ".old";
939
940 {
941 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
942 for (int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
943 EXPECT_TRUE(tree.insert(value));
944 tree.sync();
945 }
946
947 std::error_code ec;
948 fs::copy_file(tmp.path, old_snapshot, fs::copy_options::overwrite_existing, ec);
949 ASSERT_FALSE(ec) << ec.message();
950
951 {
952 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
953 EXPECT_TRUE(tree.insert(50));
954 EXPECT_TRUE(tree.insert(55));
955 tree.sync();
956 }
957
959 fs::remove(old_snapshot, ec);
960
962 EXPECT_TRUE(reopened.verify());
963 EXPECT_EQ(to_vector(reopened.range(0, 60)),
964 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}));
965 EXPECT_FALSE(fs::exists(wal));
966}
967
969{
970 TempFile tmp(make_temp_path());
971
972 {
973 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
974 for (int value = 1; value <= 80; ++value)
975 EXPECT_TRUE(tree.insert(value));
976
977 for (int value : {1, 2, 3, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 80})
978 EXPECT_TRUE(tree.remove(value));
979
980 EXPECT_TRUE(tree.verify());
981 tree.sync();
982 }
983
985 EXPECT_TRUE(reopened.verify());
986 EXPECT_EQ(reopened.min_key(), std::optional<int>(4));
987 EXPECT_EQ(reopened.max_key(), std::optional<int>(79));
988 EXPECT_EQ(to_vector(reopened.range(10, 20)),
989 (std::vector<int>{10, 11, 12, 13, 14, 18, 19, 20}));
990 EXPECT_FALSE(reopened.contains(1));
991 EXPECT_FALSE(reopened.contains(64));
992 EXPECT_TRUE(reopened.contains(66));
993}
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:351
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.
#define TEST(name)
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.
Definition ah-arena.H:89
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.
Definition ah-convert.H:238
STL namespace.
static long counter
Definition test-splice.C:35
Page-managed persistent B-Tree with file-backed storage.
Page-managed persistent B+ Tree with file-backed storage.