Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
file_b_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_bplus_tree.H>
49#include <tpl_file_b_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_btree_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_b_tree_test_file_version = 5;
147 constexpr std::uint32_t file_b_tree_test_wal_version = 4;
148 constexpr std::uint32_t file_b_tree_test_key_size =
150 constexpr std::uint64_t file_b_tree_test_min_degree = 3;
151 constexpr std::uint8_t file_b_tree_test_encoding = 3;
152
153 std::array<char, 7> file_b_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 btree_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 page_count,
168 const std::uint64_t free_page_head,
169 const std::uint64_t checkpoint_sequence,
170 const std::uint64_t dirty_count)
171 {
172 std::uint32_t crc = Aleph::detail::crc32_begin();
174
181 crc = Aleph::detail::crc32_add(crc, root_page);
182 crc = Aleph::detail::crc32_add(crc, page_count);
183 crc = Aleph::detail::crc32_add(crc, free_page_head);
184 crc = Aleph::detail::crc32_add(crc, checkpoint_sequence);
187 }
188
189 std::uint32_t btree_wal_payload_checksum(const std::uint64_t page_count,
190 const size_t page_bytes,
191 const std::vector<char> & page_blob)
192 {
193 std::uint32_t crc = Aleph::detail::crc32_begin();
194 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
195 {
196 crc = Aleph::detail::crc32_add(crc, page_id);
198 crc, page_blob.data() + (page_id - 1) * page_bytes, page_bytes);
199 }
201 }
202
203 void write_btree_wal_from_file(const fs::path & data_path,
204 const fs::path & wal_path)
205 {
206 constexpr std::uint32_t file_version = file_b_tree_test_file_version;
207 constexpr std::uint32_t wal_version = file_b_tree_test_wal_version;
208 constexpr std::uint32_t key_size = file_b_tree_test_key_size;
209 constexpr std::uint64_t min_degree = file_b_tree_test_min_degree;
210 constexpr std::uint8_t encoding = file_b_tree_test_encoding;
211 constexpr size_t max_keys = 5;
212 constexpr size_t max_children = 6;
213 constexpr size_t page_bytes =
214 sizeof(std::uint8_t) + sizeof(std::uint16_t) * 2 + 3
215 + sizeof(std::uint64_t) + sizeof(std::uint64_t) + sizeof(int) * max_keys
216 + sizeof(std::uint64_t) * max_children + sizeof(std::uint32_t);
217
218 std::ifstream in(data_path, std::ios::binary);
219 ASSERT_TRUE(in.good());
220
221 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
222 in.read(magic.data(), magic.size());
223 ASSERT_TRUE(in.good());
224
225 auto read_u32 = [&](std::uint32_t & value)
226 {
227 in.read(reinterpret_cast<char *>(&value), sizeof(value));
228 ASSERT_TRUE(in.good());
229 };
230 auto read_u64 = [&](std::uint64_t & value)
231 {
232 in.read(reinterpret_cast<char *>(&value), sizeof(value));
233 ASSERT_TRUE(in.good());
234 };
235 auto read_u8 = [&](std::uint8_t & value)
236 {
237 in.read(reinterpret_cast<char *>(&value), sizeof(value));
238 ASSERT_TRUE(in.good());
239 };
240
241 std::uint32_t version = 0;
242 std::uint32_t file_key_size = 0;
243 std::uint64_t file_min_degree = 0;
244 std::uint8_t file_encoding = 0;
245 read_u32(version);
249 ASSERT_EQ(version, file_version);
250 ASSERT_EQ(file_key_size, key_size);
251 ASSERT_EQ(file_min_degree, min_degree);
253
255 std::array<char, 7> stored_reserved = {};
256 in.read(stored_reserved.data(), stored_reserved.size());
257 ASSERT_TRUE(in.good());
259
260 std::uint64_t size = 0;
261 std::uint64_t root_page = 0;
262 std::uint64_t page_count = 0;
263 std::uint64_t free_page_head = 0;
264 std::uint64_t checkpoint_sequence = 0;
265 std::uint32_t checksum = 0;
266 read_u64(size);
267 read_u64(root_page);
268 read_u64(page_count);
269 read_u64(free_page_head);
270 read_u64(checkpoint_sequence);
272
273 std::vector<char> page_blob(page_bytes * page_count);
274 in.read(page_blob.data(), page_blob.size());
275 ASSERT_TRUE(in.good());
276
277 std::ofstream out(wal_path, std::ios::binary | std::ios::trunc);
278 ASSERT_TRUE(out.good());
279
280 const auto wal_magic =
282 const auto trailer_magic =
284 out.write(wal_magic.data(), wal_magic.size());
285 out.write(reinterpret_cast<const char *>(&wal_version), sizeof(wal_version));
286 out.write(reinterpret_cast<const char *>(&key_size), sizeof(key_size));
287 out.write(reinterpret_cast<const char *>(&min_degree), sizeof(min_degree));
288 out.write(reinterpret_cast<const char *>(&encoding), sizeof(encoding));
289 out.write(reserved.data(), reserved.size());
290 out.write(reinterpret_cast<const char *>(&size), sizeof(size));
291 out.write(reinterpret_cast<const char *>(&root_page), sizeof(root_page));
292 out.write(reinterpret_cast<const char *>(&page_count), sizeof(page_count));
293 out.write(reinterpret_cast<const char *>(&free_page_head), sizeof(free_page_head));
294 out.write(reinterpret_cast<const char *>(&checkpoint_sequence), sizeof(checkpoint_sequence));
295 out.write(reinterpret_cast<const char *>(&page_count), sizeof(page_count));
296 const auto wal_checksum =
297 btree_wal_checksum(wal_version, size, root_page, page_count, free_page_head,
298 checkpoint_sequence, page_count);
299 out.write(reinterpret_cast<const char *>(&wal_checksum), sizeof(wal_checksum));
300
301 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
302 {
303 out.write(reinterpret_cast<const char *>(&page_id), sizeof(page_id));
304 out.write(page_blob.data() + (page_id - 1) * page_bytes, page_bytes);
305 }
306
307 const auto payload_checksum =
308 btree_wal_payload_checksum(page_count, page_bytes, page_blob);
309 out.write(trailer_magic.data(), trailer_magic.size());
310 out.write(reinterpret_cast<const char *>(&checkpoint_sequence),
311 sizeof(checkpoint_sequence));
312 out.write(reinterpret_cast<const char *>(&page_count), sizeof(page_count));
313 out.write(reinterpret_cast<const char *>(&payload_checksum),
314 sizeof(payload_checksum));
315
316 ASSERT_TRUE(out.good());
317 }
318
319 std::uint64_t read_btree_page_count(const fs::path & data_path)
320 {
321 std::ifstream in(data_path, std::ios::binary);
322 EXPECT_TRUE(in.good());
323 if (not in.good())
324 return 0;
325
327 + sizeof(std::uint32_t) * 2 + sizeof(std::uint64_t)
328 + sizeof(std::uint8_t) + 7 + sizeof(std::uint64_t) * 2);
329 EXPECT_TRUE(in.good());
330 std::uint64_t page_count = 0;
331 in.read(reinterpret_cast<char *>(&page_count), sizeof(page_count));
332 EXPECT_TRUE(in.good());
333 return page_count;
334 }
335
336 int read_btree_root_first_key(const fs::path & data_path)
337 {
338 constexpr size_t header_bytes =
339 Aleph::detail::Ordered_Tree_Snapshot_Magic_Size + sizeof(std::uint32_t) * 3
340 + sizeof(std::uint64_t) * 5 + sizeof(std::uint8_t) + 7;
341 constexpr size_t page_bytes =
342 sizeof(std::uint8_t) + sizeof(std::uint16_t) * 2 + 3
343 + sizeof(std::uint64_t) + sizeof(std::uint64_t) + sizeof(int) * 5
344 + sizeof(std::uint64_t) * 6 + sizeof(std::uint32_t);
345
346 std::ifstream in(data_path, std::ios::binary);
347 EXPECT_TRUE(in.good());
348 if (not in.good())
349 return 0;
350
352 + sizeof(std::uint32_t) * 2 + sizeof(std::uint64_t)
353 + sizeof(std::uint8_t) + 7 + sizeof(std::uint64_t));
354 EXPECT_TRUE(in.good());
355
356 std::uint64_t root_page = 0;
357 in.read(reinterpret_cast<char *>(&root_page), sizeof(root_page));
358 EXPECT_TRUE(in.good());
359
360 const auto offset = static_cast<std::streamoff>(
361 header_bytes + (root_page - 1) * page_bytes + sizeof(std::uint8_t)
362 + sizeof(std::uint16_t) * 2 + 3 + sizeof(std::uint64_t) * 2);
363 in.seekg(offset);
364 EXPECT_TRUE(in.good());
365
366 std::array<unsigned char, Aleph::detail::Paged_Value_Codec<int>::encoded_size>
367 key_bytes = {};
368 in.read(reinterpret_cast<char *>(key_bytes.data()), key_bytes.size());
369 EXPECT_TRUE(in.good());
371 }
372
373 std::vector<std::uint64_t> read_btree_wal_page_ids(const fs::path & wal_path)
374 {
375 constexpr size_t page_bytes =
376 sizeof(std::uint8_t) + sizeof(std::uint16_t) * 2 + 3
377 + sizeof(std::uint64_t) + sizeof(std::uint64_t) + sizeof(int) * 5
378 + sizeof(std::uint64_t) * 6 + sizeof(std::uint32_t);
379
380 std::ifstream in(wal_path, std::ios::binary);
381 EXPECT_TRUE(in.good());
382 std::vector<std::uint64_t> page_ids;
383 if (not in.good())
384 return page_ids;
385
386 auto read_u32 = [&](std::uint32_t & value)
387 {
388 in.read(reinterpret_cast<char *>(&value), sizeof(value));
389 EXPECT_TRUE(in.good());
390 };
391 auto read_u64 = [&](std::uint64_t & value)
392 {
393 in.read(reinterpret_cast<char *>(&value), sizeof(value));
394 EXPECT_TRUE(in.good());
395 };
396 auto read_u8 = [&](std::uint8_t & value)
397 {
398 in.read(reinterpret_cast<char *>(&value), sizeof(value));
399 EXPECT_TRUE(in.good());
400 };
401
402 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
403 std::array<char, 7> reserved = {};
404 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size>
405 trailer_magic = {};
406 std::uint32_t wal_version = 0;
407 std::uint32_t key_size = 0;
408 std::uint64_t min_degree = 0;
409 std::uint8_t encoding = 0;
410 std::uint64_t ignored64 = 0;
411 std::uint32_t ignored32 = 0;
412 std::uint64_t dirty_count = 0;
413
414 in.read(magic.data(), magic.size());
415 EXPECT_TRUE(in.good());
417 read_u32(key_size);
418 read_u64(min_degree);
420 in.read(reserved.data(), reserved.size());
421 EXPECT_TRUE(in.good());
422 read_u64(ignored64); // size
423 read_u64(ignored64); // root page
424 read_u64(ignored64); // page count
425 read_u64(ignored64); // free page head
426 read_u64(ignored64); // checkpoint sequence
428 read_u32(ignored32); // header checksum
429
430 page_ids.reserve(dirty_count);
431 for (std::uint64_t i = 0; i < dirty_count; ++i)
432 {
433 std::uint64_t page_id = 0;
434 read_u64(page_id);
435 page_ids.push_back(page_id);
436 in.seekg(static_cast<std::streamoff>(page_bytes), std::ios::cur);
437 EXPECT_TRUE(in.good());
438 }
439
440 in.read(trailer_magic.data(), trailer_magic.size());
441 EXPECT_TRUE(in.good());
442 return page_ids;
443 }
444
445 void copy_region(const fs::path & src, const fs::path & dst,
446 const std::streamoff offset, const size_t size)
447 {
448 std::ifstream in(src, std::ios::binary);
449 ASSERT_TRUE(in.good());
450 std::fstream out(dst, std::ios::binary | std::ios::in | std::ios::out);
451 ASSERT_TRUE(out.good());
452
453 std::vector<char> buffer(size);
454 in.seekg(offset);
455 ASSERT_TRUE(in.good());
456 in.read(buffer.data(), buffer.size());
457 ASSERT_TRUE(in.good());
458
459 out.seekp(offset);
460 ASSERT_TRUE(out.good());
461 out.write(buffer.data(), buffer.size());
462 ASSERT_TRUE(out.good());
463 }
464}
465
467{
468 TempFile tmp(make_temp_path());
469
470 {
471 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
472 for (int value : {40, 10, 90, 20, 70, 60, 30})
473 EXPECT_TRUE(tree.insert(value));
474
475 EXPECT_TRUE(tree.verify());
476 EXPECT_EQ(to_vector(tree.keys()),
477 (std::vector<int>{10, 20, 30, 40, 60, 70, 90}));
478 }
479
481 EXPECT_TRUE(reopened.verify());
483 (std::vector<int>{10, 20, 30, 40, 60, 70, 90}));
484 EXPECT_EQ(reopened.lower_bound(55), std::optional<int>(60));
485 EXPECT_EQ(reopened.upper_bound(60), std::optional<int>(70));
486}
487
489{
490 TempFile tmp(make_temp_path());
491
492 {
493 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
495 EXPECT_TRUE(tree.insert(10));
496 EXPECT_TRUE(tree.insert(20));
497 }
498
499 {
501 EXPECT_TRUE(reopened.is_empty());
502 }
503
504 {
505 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
506 tree.set_auto_sync(false);
507 EXPECT_TRUE(tree.insert(10));
508 EXPECT_TRUE(tree.insert(20));
509 tree.sync();
510 }
511
513 EXPECT_EQ(to_vector(reopened.keys()), (std::vector<int>{10, 20}));
514}
515
517{
518 TempFile tmp(make_temp_path());
519
520 std::uint64_t first = 0;
521 std::uint64_t second = 0;
522
523 {
524 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
525 EXPECT_EQ(tree.checkpoint_sequence(), 1u);
526 EXPECT_TRUE(tree.insert(10));
527 tree.checkpoint();
528 first = tree.checkpoint_sequence();
529 EXPECT_GT(first, 1u);
530
531 EXPECT_TRUE(tree.insert(20));
532 tree.sync();
533 second = tree.checkpoint_sequence();
534 EXPECT_GT(second, first);
535 }
536
538 EXPECT_EQ(reopened.checkpoint_sequence(), second);
539}
540
542{
543 TempFile tmp(make_temp_path());
545 const std::vector<std::string> expected = {
546 "alfa", "beta", "delta", "epsilon", "gama"
547 };
548
549 {
551 tree(tmp.path.string(), false);
552 EXPECT_TRUE(tree.insert("delta"));
553 EXPECT_TRUE(tree.insert("alfa"));
554 EXPECT_TRUE(tree.insert("gama"));
555 EXPECT_TRUE(tree.insert("beta"));
556 EXPECT_TRUE(tree.insert("epsilon"));
557 EXPECT_TRUE(tree.verify());
558 tree.sync();
559 }
560
562 reopened(tmp.path.string(),
565 EXPECT_TRUE(reopened.verify());
567 EXPECT_EQ(reopened.lower_bound(std::string("carrot")),
568 std::optional<std::string>("delta"));
569}
570
572{
573 TempFile tmp(make_temp_path());
574
575 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
576 EXPECT_THROW((File_B_Tree<int, Aleph::less<int>, 3>(tmp.path.string(), false)),
577 std::runtime_error);
578}
579
581{
582 TempFile tmp(make_temp_path());
583
584 {
585 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
586 EXPECT_TRUE(tree.insert(10));
587 EXPECT_TRUE(tree.insert(20));
588 tree.sync();
589 }
590
592 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Only);
594 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Only);
596
597 EXPECT_TRUE(reader1.is_read_only());
598 EXPECT_EQ(reader1.open_mode(), read_only_mode);
599 EXPECT_EQ(to_vector(reader2.keys()), (std::vector<int>{10, 20}));
601 tmp.path.string(),
603 std::runtime_error);
604}
605
607{
608 TempFile tmp(make_temp_path());
609
610 {
611 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
612 EXPECT_TRUE(tree.insert(10));
613 tree.sync();
614 }
615
617 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Only);
618
619 EXPECT_THROW(reader.insert(20), std::runtime_error);
620 EXPECT_THROW(reader.remove(10), std::runtime_error);
621 EXPECT_THROW(reader.clear(), std::runtime_error);
622 EXPECT_THROW(reader.sync(), std::runtime_error);
623 EXPECT_EQ(to_vector(reader.keys()), (std::vector<int>{10}));
624}
625
627{
628 TempFile tmp(make_temp_path());
629
630 {
631 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
632 EXPECT_TRUE(tree.insert(10));
633 tree.sync();
634 }
635
636 {
637 std::ofstream out(tmp.path.string() + ".wal",
638 std::ios::binary | std::ios::trunc);
639 ASSERT_TRUE(out.good());
640 out << "pending recovery";
641 }
642
644 tmp.path.string(),
646 std::runtime_error);
647}
648
650{
651#if defined(__unix__) || defined(__APPLE__)
652 if (running_under_tsan())
653 GTEST_SKIP() << "TSAN does not support these fork-based lock checks";
654
655 TempFile tmp(make_temp_path());
656
657 {
658 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
659 EXPECT_TRUE(tree.insert(10));
660 tree.sync();
661 }
662
664 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Only);
665
666 const int shared_status = run_in_child([&]() -> int
667 {
668 try
669 {
671 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Only);
672 return child.contains(10) ? 0 : 2;
673 }
674 catch (...)
675 {
676 return 1;
677 }
678 });
682
683 const int writer_status = run_in_child([&]() -> int
684 {
685 try
686 {
688 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Write);
689 return 1;
690 }
691 catch (const std::runtime_error &)
692 {
693 return 0;
694 }
695 catch (...)
696 {
697 return 2;
698 }
699 });
703#else
704 GTEST_SKIP() << "fork-based lock validation is only available on Unix-like systems";
705#endif
706}
707
709{
710 TempFile tmp(make_temp_path());
711
712 {
713 std::ofstream out(tmp.path.string() + ".lock",
714 std::ios::binary | std::ios::trunc);
715 ASSERT_TRUE(out.good());
716 out << "stale\n";
717 }
718
719 {
720 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
721 EXPECT_TRUE(tree.insert(5));
722 tree.sync();
723 }
724
725 File_B_Tree<int, Aleph::less<int>, 3> reopened(tmp.path.string(), false);
726 EXPECT_EQ(to_vector(reopened.keys()), (std::vector<int>{5}));
727}
728
730{
731 TempFile tmp(make_temp_path());
732
733 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
734 EXPECT_TRUE(tree.insert(5));
735 EXPECT_TRUE(tree.insert(15));
736 tree.sync();
737
738 EXPECT_TRUE(tree.insert(25));
739 EXPECT_TRUE(tree.remove(5));
740 EXPECT_EQ(to_vector(tree.keys()), (std::vector<int>{15, 25}));
741
742 tree.reload();
743 EXPECT_EQ(to_vector(tree.keys()), (std::vector<int>{5, 15}));
744 EXPECT_TRUE(tree.verify());
745}
746
748{
749 TempFile tmp(make_temp_path());
750
751 {
752 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
753 out << "not a valid Aleph snapshot";
754 }
755
756 EXPECT_THROW((File_B_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
757 std::runtime_error);
758}
759
761{
762 TempFile tmp(make_temp_path());
763
764 {
765 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
766 for (int value : {10, 20, 30, 40, 50})
767 EXPECT_TRUE(tree.insert(value));
768 }
769
770 std::fstream io(tmp.path, std::ios::binary | std::ios::in | std::ios::out);
771 ASSERT_TRUE(io.good());
772 io.seekg(0, std::ios::end);
773 const auto end = io.tellg();
774 ASSERT_GT(end, 0);
775 io.seekg(end - std::streamoff(1));
776
777 char byte = 0;
778 io.read(&byte, 1);
779 ASSERT_TRUE(io.good());
780 byte ^= static_cast<char>(0x5A);
781 io.seekp(end - std::streamoff(1));
782 io.write(&byte, 1);
783 ASSERT_TRUE(io.good());
784 io.close();
785
786 EXPECT_THROW((File_B_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
787 std::runtime_error);
788}
789
791{
792 TempFile tmp(make_temp_path());
793 const auto journal = tmp.path.string() + ".journal";
794
795 {
796 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
797 for (int value : {12, 6, 18, 3, 9, 15, 21})
798 EXPECT_TRUE(tree.insert(value));
799 tree.sync();
800 }
801
802 std::error_code ec;
803 fs::copy_file(tmp.path, journal, fs::copy_options::overwrite_existing, ec);
804 ASSERT_FALSE(ec) << ec.message();
805
806 {
807 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
808 ASSERT_TRUE(out.good());
809 out << "corrupt";
810 }
811
813 EXPECT_TRUE(reopened.verify());
815 (std::vector<int>{3, 6, 9, 12, 15, 18, 21}));
816 EXPECT_FALSE(fs::exists(journal));
817}
818
820{
821 TempFile tmp(make_temp_path());
822 const auto wal = tmp.path.string() + ".wal";
823
824 {
825 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
826 for (int value : {12, 6, 18, 3, 9, 15, 21})
827 EXPECT_TRUE(tree.insert(value));
828 }
829
831
832 {
833 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
834 ASSERT_TRUE(out.good());
835 out << "corrupt";
836 }
837
839 EXPECT_TRUE(reopened.verify());
841 (std::vector<int>{3, 6, 9, 12, 15, 18, 21}));
842 EXPECT_FALSE(fs::exists(wal));
843}
844
846{
847 TempFile tmp(make_temp_path());
848 const auto wal = tmp.path.string() + ".wal";
849
850 {
851 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
852 for (int value : {12, 6, 18, 3, 9, 15, 21})
853 EXPECT_TRUE(tree.insert(value));
854 }
855
857
858 std::fstream wal_io(wal, std::ios::binary | std::ios::in | std::ios::out);
859 ASSERT_TRUE(wal_io.good());
860 wal_io.seekg(0, std::ios::end);
861 const auto wal_end = wal_io.tellg();
862 ASSERT_GT(wal_end, 0);
863 wal_io.seekg(wal_end - std::streamoff(1));
864
865 char byte = 0;
866 wal_io.read(&byte, 1);
867 ASSERT_TRUE(wal_io.good());
868 byte ^= static_cast<char>(0x11);
869 wal_io.seekp(wal_end - std::streamoff(1));
870 wal_io.write(&byte, 1);
871 ASSERT_TRUE(wal_io.good());
872 wal_io.close();
873
874 {
875 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
876 ASSERT_TRUE(out.good());
877 out << "corrupt";
878 }
879
880 EXPECT_THROW((File_B_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
881 std::runtime_error);
882}
883
885{
886 constexpr size_t header_bytes =
887 Aleph::detail::Ordered_Tree_Snapshot_Magic_Size + sizeof(std::uint32_t) * 3
888 + sizeof(std::uint64_t) * 5 + sizeof(std::uint8_t) + 7;
889 constexpr size_t page_bytes =
890 sizeof(std::uint8_t) + sizeof(std::uint16_t) * 2 + 3
891 + sizeof(std::uint64_t) + sizeof(std::uint64_t) + sizeof(int) * 5
892 + sizeof(std::uint64_t) * 6 + sizeof(std::uint32_t);
893
894 TempFile tmp(make_temp_path());
895 const auto wal = tmp.path.string() + ".wal";
896 const auto old_snapshot = tmp.path.string() + ".old";
897 const auto new_snapshot = tmp.path.string() + ".new";
898
899 {
900 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
901 for (int value = 1; value <= 40; ++value)
902 EXPECT_TRUE(tree.insert(value));
903 tree.sync();
904 }
905
906 std::error_code ec;
907 fs::copy_file(tmp.path, old_snapshot, fs::copy_options::overwrite_existing, ec);
908 ASSERT_FALSE(ec) << ec.message();
909
910 const int root_key = read_btree_root_first_key(tmp.path);
911
912 {
913 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
915 tree.sync();
916 }
917
918 fs::copy_file(tmp.path, new_snapshot, fs::copy_options::overwrite_existing, ec);
919 ASSERT_FALSE(ec) << ec.message();
921
924 ASSERT_GE(dirty_pages.size(), 2u);
925
926 fs::copy_file(old_snapshot, tmp.path, fs::copy_options::overwrite_existing, ec);
927 ASSERT_FALSE(ec) << ec.message();
928 copy_region(new_snapshot, tmp.path, 0, header_bytes);
930 static_cast<std::streamoff>(header_bytes
931 + (dirty_pages.front() - 1) * page_bytes),
932 page_bytes);
933
936 EXPECT_TRUE(reopened.verify());
938 EXPECT_FALSE(fs::exists(wal));
939}
940
942{
943 TempFile tmp(make_temp_path());
944 const auto wal = tmp.path.string() + ".wal";
945 const auto old_snapshot = tmp.path.string() + ".old";
946
947 {
948 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
949 for (int value : {12, 6, 18, 3, 9, 15, 21})
950 EXPECT_TRUE(tree.insert(value));
951 tree.sync();
952 }
953
954 std::error_code ec;
955 fs::copy_file(tmp.path, old_snapshot, fs::copy_options::overwrite_existing, ec);
956 ASSERT_FALSE(ec) << ec.message();
957
958 {
959 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
960 EXPECT_TRUE(tree.insert(24));
961 EXPECT_TRUE(tree.insert(27));
962 tree.sync();
963 }
964
966 fs::remove(old_snapshot, ec);
967
969 EXPECT_TRUE(reopened.verify());
971 (std::vector<int>{3, 6, 9, 12, 15, 18, 21, 24, 27}));
972 EXPECT_FALSE(fs::exists(wal));
973}
974
976{
977 TempFile tmp(make_temp_path());
978
979 {
980 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
981 EXPECT_TRUE(tree.insert(1));
982 EXPECT_TRUE(tree.insert(2));
983 }
984
985 EXPECT_THROW((File_B_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
986 std::runtime_error);
987}
988
990{
991 TempFile tmp(make_temp_path());
992
993 {
994 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
995 for (int value = 1; value <= 80; ++value)
996 EXPECT_TRUE(tree.insert(value));
997
998 for (int value : {1, 2, 3, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 80})
999 EXPECT_TRUE(tree.remove(value));
1000
1001 EXPECT_TRUE(tree.verify());
1002 tree.sync();
1003 }
1004
1006 EXPECT_TRUE(reopened.verify());
1007 EXPECT_EQ(reopened.min_key(), std::optional<int>(4));
1008 EXPECT_EQ(reopened.max_key(), std::optional<int>(79));
1010 (std::vector<int>{
1011 4, 5, 6, 10, 11, 12, 13, 14, 18, 19, 20, 21, 22, 23, 24, 25,
1012 26, 27, 28, 29, 30, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
1013 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
1014 61, 62, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79
1015 }));
1016}
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
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.
Persistent page-managed B-Tree stored in a single binary file.
bool verify() const
Verify structural B-Tree invariants across cached pages.
void set_auto_sync(const bool enabled) noexcept
Enable or disable automatic flushing of dirty pages.
bool contains(const Key &key) const
Return whether a key is present.
bool auto_sync_enabled() const noexcept
Return whether automatic synchronization is enabled.
Array< Key > keys() const
Materialize the tree contents in sorted order.
void checkpoint() const
Synonym for sync().
void sync() const
Flush the current tree image using redo WAL or full-image fallback.
bool remove(const Key &key)
Remove a key if present.
void reload()
Discard unsynchronized changes and reload pages from disk.
std::uint64_t checkpoint_sequence() const noexcept
Return the durable checkpoint sequence stored in the backing 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.