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#elif defined(_WIN32)
55# include <process.h>
56#endif
57
58using namespace Aleph;
59
60namespace
61{
62 namespace fs = std::filesystem;
63
65 long long process_id() noexcept
66 {
67#if defined(_WIN32)
68 return static_cast<long long>(_getpid());
69#else
70 return static_cast<long long>(getpid());
71#endif
72 }
73
74 fs::path make_temp_path()
75 {
76 // A steady_clock tick plus a per-process counter is unique *within*
77 // a process, but not across the several processes that actually
78 // run this suite (each TEST() is its own ctest process, and CI
79 // runs ctest with --parallel, so every process's counter restarts
80 // at 0) -- if two processes' first call lands in the same clock
81 // tick, they produce the identical id and race on the same file.
82 // Mixing in the process id closes that gap regardless of clock
83 // resolution.
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");
91 }
92
93 struct TempFile
94 {
95 fs::path path;
96
97 explicit TempFile(fs::path p) : path(std::move(p)) {}
98
99 ~TempFile()
100 {
101 std::error_code ec;
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);
110 }
111 };
112
113 template <typename T>
114 std::vector<T> to_vector(const Array<T> & arr)
115 {
116 std::vector<T> ret;
117 ret.reserve(arr.size());
118 for (size_t i = 0; i < arr.size(); ++i)
119 ret.push_back(arr[i]);
120 return ret;
121 }
122
123#if defined(__unix__) || defined(__APPLE__)
124 constexpr bool running_under_tsan() noexcept
125 {
126# if defined(__SANITIZE_THREAD__)
127 return true;
128# elif defined(__has_feature)
129# if __has_feature(thread_sanitizer)
130 return true;
131# else
132 return false;
133# endif
134# else
135 return false;
136# endif
137 }
138
139 template <typename F>
140 int run_in_child(F && fn)
141 {
142 const pid_t pid = ::fork();
143 if (pid < 0)
144 return -1;
145
146 if (pid == 0)
147 {
148 int code = 100;
149 try
150 {
151 code = fn();
152 }
153 catch (...)
154 {
155 code = 101;
156 }
157 ::_exit(code);
158 }
159
160 int status = 0;
161 if (::waitpid(pid, &status, 0) < 0)
162 return -1;
163 return status;
164 }
165#endif
166
167 constexpr std::uint32_t file_bplus_tree_test_file_version = 5;
168 constexpr std::uint32_t file_bplus_tree_test_wal_version = 4;
169 constexpr std::uint32_t file_bplus_tree_test_key_size =
171 constexpr std::uint64_t file_bplus_tree_test_min_degree = 3;
172 constexpr std::uint8_t file_bplus_tree_test_encoding = 3;
173
174 std::array<char, 7> file_bplus_tree_reserved_bytes()
175 {
176 std::array<char, 7> reserved = {};
178 reserved[0] = static_cast<char>(codec_id & 0xFFu);
179 reserved[1] = static_cast<char>((codec_id >> 8) & 0xFFu);
180 reserved[2] = static_cast<char>((codec_id >> 16) & 0xFFu);
181 reserved[3] = static_cast<char>((codec_id >> 24) & 0xFFu);
182 return reserved;
183 }
184
185 std::uint32_t bplus_wal_checksum(const std::uint32_t wal_version,
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,
192 const std::uint64_t dirty_count)
193 {
194 std::uint32_t crc = Aleph::detail::crc32_begin();
196
203 crc = Aleph::detail::crc32_add(crc, root_page);
204 crc = Aleph::detail::crc32_add(crc, first_leaf_page);
205 crc = Aleph::detail::crc32_add(crc, page_count);
206 crc = Aleph::detail::crc32_add(crc, free_page_head);
207 crc = Aleph::detail::crc32_add(crc, checkpoint_sequence);
210 }
211
212 std::uint32_t bplus_wal_payload_checksum(const std::uint64_t page_count,
213 const size_t page_bytes,
214 const std::vector<char> & page_blob)
215 {
216 std::uint32_t crc = Aleph::detail::crc32_begin();
217 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
218 {
219 crc = Aleph::detail::crc32_add(crc, page_id);
221 crc, page_blob.data() + (page_id - 1) * page_bytes, page_bytes);
222 }
224 }
225
226 void write_bplus_wal_from_file(const fs::path & data_path,
227 const fs::path & wal_path)
228 {
229 constexpr std::uint32_t file_version = file_bplus_tree_test_file_version;
230 constexpr std::uint32_t wal_version = file_bplus_tree_test_wal_version;
231 constexpr std::uint32_t key_size = file_bplus_tree_test_key_size;
232 constexpr std::uint64_t min_degree = file_bplus_tree_test_min_degree;
233 constexpr std::uint8_t encoding = file_bplus_tree_test_encoding;
234 constexpr size_t max_keys = 5;
235 constexpr size_t max_children = 6;
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);
240
241 std::ifstream in(data_path, std::ios::binary);
242 ASSERT_TRUE(in.good());
243
244 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
245 in.read(magic.data(), magic.size());
246 ASSERT_TRUE(in.good());
247
248 auto read_u32 = [&](std::uint32_t & value)
249 {
250 in.read(reinterpret_cast<char *>(&value), sizeof(value));
251 ASSERT_TRUE(in.good());
252 };
253 auto read_u64 = [&](std::uint64_t & value)
254 {
255 in.read(reinterpret_cast<char *>(&value), sizeof(value));
256 ASSERT_TRUE(in.good());
257 };
258 auto read_u8 = [&](std::uint8_t & value)
259 {
260 in.read(reinterpret_cast<char *>(&value), sizeof(value));
261 ASSERT_TRUE(in.good());
262 };
263
264 std::uint32_t version = 0;
265 std::uint32_t file_key_size = 0;
266 std::uint64_t file_min_degree = 0;
267 std::uint8_t file_encoding = 0;
268 read_u32(version);
272 ASSERT_EQ(version, file_version);
273 ASSERT_EQ(file_key_size, key_size);
274 ASSERT_EQ(file_min_degree, min_degree);
276
278 std::array<char, 7> stored_reserved = {};
279 in.read(stored_reserved.data(), stored_reserved.size());
280 ASSERT_TRUE(in.good());
282
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;
289 std::uint32_t checksum = 0;
290 read_u64(size);
291 read_u64(root_page);
292 read_u64(first_leaf_page);
293 read_u64(page_count);
294 read_u64(free_page_head);
295 read_u64(checkpoint_sequence);
297
298 std::vector<char> page_blob(page_bytes * page_count);
299 in.read(page_blob.data(), page_blob.size());
300 ASSERT_TRUE(in.good());
301
302 std::ofstream out(wal_path, std::ios::binary | std::ios::trunc);
303 ASSERT_TRUE(out.good());
304
305 const auto wal_magic =
307 const auto trailer_magic =
309 out.write(wal_magic.data(), wal_magic.size());
310 out.write(reinterpret_cast<const char *>(&wal_version), sizeof(wal_version));
311 out.write(reinterpret_cast<const char *>(&key_size), sizeof(key_size));
312 out.write(reinterpret_cast<const char *>(&min_degree), sizeof(min_degree));
313 out.write(reinterpret_cast<const char *>(&encoding), sizeof(encoding));
314 out.write(reserved.data(), reserved.size());
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));
322 const auto wal_checksum = bplus_wal_checksum(
324 size, root_page, first_leaf_page, page_count, free_page_head,
325 checkpoint_sequence, page_count);
326 out.write(reinterpret_cast<const char *>(&wal_checksum), sizeof(wal_checksum));
327
328 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
329 {
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);
332 }
333
334 const auto payload_checksum =
335 bplus_wal_payload_checksum(page_count, page_bytes, page_blob);
336 out.write(trailer_magic.data(), trailer_magic.size());
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));
340 out.write(reinterpret_cast<const char *>(&payload_checksum),
341 sizeof(payload_checksum));
342
343 ASSERT_TRUE(out.good());
344 }
345
346 std::uint64_t read_bplus_page_count(const fs::path & data_path)
347 {
348 std::ifstream in(data_path, std::ios::binary);
349 EXPECT_TRUE(in.good());
350 if (not in.good())
351 return 0;
352
354 + sizeof(std::uint32_t) * 2 + sizeof(std::uint64_t)
355 + sizeof(std::uint8_t) + 7 + sizeof(std::uint64_t) * 3);
356 EXPECT_TRUE(in.good());
357 std::uint64_t page_count = 0;
358 in.read(reinterpret_cast<char *>(&page_count), sizeof(page_count));
359 EXPECT_TRUE(in.good());
360 return page_count;
361 }
362
363 std::vector<std::uint64_t> read_bplus_wal_page_ids(const fs::path & wal_path)
364 {
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);
369
370 std::ifstream in(wal_path, std::ios::binary);
371 EXPECT_TRUE(in.good());
372 std::vector<std::uint64_t> page_ids;
373 if (not in.good())
374 return page_ids;
375
376 auto read_u32 = [&](std::uint32_t & value)
377 {
378 in.read(reinterpret_cast<char *>(&value), sizeof(value));
379 EXPECT_TRUE(in.good());
380 };
381 auto read_u64 = [&](std::uint64_t & value)
382 {
383 in.read(reinterpret_cast<char *>(&value), sizeof(value));
384 EXPECT_TRUE(in.good());
385 };
386 auto read_u8 = [&](std::uint8_t & value)
387 {
388 in.read(reinterpret_cast<char *>(&value), sizeof(value));
389 EXPECT_TRUE(in.good());
390 };
391
392 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
393 std::array<char, 7> reserved = {};
394 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size>
395 trailer_magic = {};
396 std::uint32_t wal_version = 0;
397 std::uint32_t key_size = 0;
398 std::uint64_t min_degree = 0;
399 std::uint8_t encoding = 0;
400 std::uint64_t ignored64 = 0;
401 std::uint32_t ignored32 = 0;
402 std::uint64_t dirty_count = 0;
403
404 in.read(magic.data(), magic.size());
405 EXPECT_TRUE(in.good());
407 read_u32(key_size);
408 read_u64(min_degree);
410 in.read(reserved.data(), reserved.size());
411 EXPECT_TRUE(in.good());
412 read_u64(ignored64); // size
413 read_u64(ignored64); // root page
414 read_u64(ignored64); // first leaf
415 read_u64(ignored64); // page count
416 read_u64(ignored64); // free page head
417 read_u64(ignored64); // checkpoint sequence
419 read_u32(ignored32); // header checksum
420
421 page_ids.reserve(dirty_count);
422 for (std::uint64_t i = 0; i < dirty_count; ++i)
423 {
424 std::uint64_t page_id = 0;
425 read_u64(page_id);
426 page_ids.push_back(page_id);
427 in.seekg(static_cast<std::streamoff>(page_bytes), std::ios::cur);
428 EXPECT_TRUE(in.good());
429 }
430
431 in.read(trailer_magic.data(), trailer_magic.size());
432 EXPECT_TRUE(in.good());
433 return page_ids;
434 }
435
436 void copy_region(const fs::path & src, const fs::path & dst,
437 const std::streamoff offset, const size_t size)
438 {
439 std::ifstream in(src, std::ios::binary);
440 ASSERT_TRUE(in.good());
441 std::fstream out(dst, std::ios::binary | std::ios::in | std::ios::out);
442 ASSERT_TRUE(out.good());
443
444 std::vector<char> buffer(size);
445 in.seekg(offset);
446 ASSERT_TRUE(in.good());
447 in.read(buffer.data(), buffer.size());
448 ASSERT_TRUE(in.good());
449
450 out.seekp(offset);
451 ASSERT_TRUE(out.good());
452 out.write(buffer.data(), buffer.size());
453 ASSERT_TRUE(out.good());
454 }
455}
456
458{
459 TempFile tmp(make_temp_path());
460
461 {
462 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
463 for (int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
464 EXPECT_TRUE(tree.insert(value));
465
466 EXPECT_TRUE(tree.verify());
467 EXPECT_EQ(to_vector(tree.range(118, 142)),
468 (std::vector<int>{120, 125, 130, 135, 140}));
469 }
470
472 EXPECT_TRUE(reopened.verify());
474 (std::vector<int>{105, 110, 115, 120, 125, 130, 135, 140, 145, 150}));
475 EXPECT_EQ(to_vector(reopened.range(118, 142)),
476 (std::vector<int>{120, 125, 130, 135, 140}));
477 EXPECT_EQ(reopened.lower_bound(126), std::optional<int>(130));
478 EXPECT_EQ(reopened.upper_bound(140), std::optional<int>(145));
479}
480
482{
483 TempFile tmp(make_temp_path());
484
485 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
486 for (int value : {10, 20, 30, 40, 50})
487 EXPECT_TRUE(tree.insert(value));
488 tree.sync();
489
490 EXPECT_TRUE(tree.insert(60));
491 EXPECT_TRUE(tree.remove(20));
492 EXPECT_EQ(to_vector(tree.range(10, 70)),
493 (std::vector<int>{10, 30, 40, 50, 60}));
494
495 tree.reload();
496 EXPECT_EQ(to_vector(tree.range(10, 70)),
497 (std::vector<int>{10, 20, 30, 40, 50}));
498 EXPECT_TRUE(tree.verify());
499}
500
502{
503 TempFile tmp(make_temp_path());
504
505 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
506 for (int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
507 EXPECT_TRUE(tree.insert(value));
508
509 std::vector<int> all_keys;
510 for (auto it = tree.get_it(); it.has_curr(); it.next_ne())
511 all_keys.push_back(it.get_curr());
513 (std::vector<int>{105, 110, 115, 120, 125, 130, 135, 140, 145, 150}));
514
515 std::vector<int> band;
516 auto band_it = tree.get_range_it(118, 141);
517 ASSERT_TRUE(band_it.has_curr());
518 EXPECT_EQ(*band_it, 120);
519 EXPECT_EQ(band_it.get_curr(), 120);
520 band.push_back(band_it.get_curr());
521 band_it.next();
522 for (; band_it.has_curr(); band_it.next_ne())
523 band.push_back(band_it.get_curr());
524
525 EXPECT_EQ(band, (std::vector<int>{120, 125, 130, 135, 140}));
526}
527
529{
530 TempFile tmp(make_temp_path());
532 const std::vector<std::string> expected = {
533 "alfa", "beta", "delta", "epsilon", "gama"
534 };
535
536 {
538 tree(tmp.path.string(), false);
539 EXPECT_TRUE(tree.insert("delta"));
540 EXPECT_TRUE(tree.insert("alfa"));
541 EXPECT_TRUE(tree.insert("gama"));
542 EXPECT_TRUE(tree.insert("beta"));
543 EXPECT_TRUE(tree.insert("epsilon"));
544 EXPECT_TRUE(tree.verify());
545 EXPECT_EQ(to_vector(tree.range(std::string("beta"), std::string("epsilon"))),
546 (std::vector<std::string>{"beta", "delta", "epsilon"}));
547 tree.sync();
548 }
549
551 reopened(tmp.path.string(),
554 EXPECT_TRUE(reopened.verify());
556
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"}));
563}
564
566{
567 TempFile tmp(make_temp_path());
569
571 tree(tmp.path.string(), false);
572 EXPECT_TRUE(tree.insert("demasiado-largo"));
573 EXPECT_THROW(tree.sync(), std::runtime_error);
574 EXPECT_TRUE(tree.verify());
575}
576
578{
579 TempFile tmp(make_temp_path());
580
581 std::uint64_t first = 0;
582 std::uint64_t second = 0;
583
584 {
585 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
586 EXPECT_EQ(tree.checkpoint_sequence(), 1u);
587 EXPECT_TRUE(tree.insert(10));
588 tree.checkpoint();
589 first = tree.checkpoint_sequence();
590 EXPECT_GT(first, 1u);
591
592 EXPECT_TRUE(tree.insert(20));
593 tree.sync();
594 second = tree.checkpoint_sequence();
595 EXPECT_GT(second, first);
596 }
597
599 EXPECT_EQ(reopened.checkpoint_sequence(), second);
600}
601
603{
604 TempFile tmp(make_temp_path());
605
606 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
607 EXPECT_THROW((File_BPlus_Tree<int, Aleph::less<int>, 3>(tmp.path.string(), false)),
608 std::runtime_error);
609}
610
612{
613 TempFile tmp(make_temp_path());
614
615 {
616 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
617 for (int value : {10, 20, 30, 40})
618 EXPECT_TRUE(tree.insert(value));
619 tree.sync();
620 }
621
623 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Only);
625 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Only);
626 const auto read_only_mode =
628
629 EXPECT_TRUE(reader1.is_read_only());
630 EXPECT_EQ(reader1.open_mode(), read_only_mode);
631 EXPECT_EQ(to_vector(reader2.range(0, 50)), (std::vector<int>{10, 20, 30, 40}));
633 tmp.path.string(),
635 std::runtime_error);
636}
637
639{
640 TempFile tmp(make_temp_path());
641
642 {
643 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
644 EXPECT_TRUE(tree.insert(10));
645 tree.sync();
646 }
647
649 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Only);
650
651 EXPECT_THROW(reader.insert(20), std::runtime_error);
652 EXPECT_THROW(reader.remove(10), std::runtime_error);
653 EXPECT_THROW(reader.clear(), std::runtime_error);
654 EXPECT_THROW(reader.sync(), std::runtime_error);
655 EXPECT_EQ(to_vector(reader.range(0, 20)), (std::vector<int>{10}));
656}
657
659{
660 TempFile tmp(make_temp_path());
661
662 {
663 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
664 EXPECT_TRUE(tree.insert(10));
665 tree.sync();
666 }
667
668 {
669 std::ofstream out(tmp.path.string() + ".wal",
670 std::ios::binary | std::ios::trunc);
671 ASSERT_TRUE(out.good());
672 out << "pending recovery";
673 }
674
676 tmp.path.string(),
678 std::runtime_error);
679}
680
682{
683#if defined(__unix__) || defined(__APPLE__)
684 if (running_under_tsan())
685 GTEST_SKIP() << "TSAN does not support these fork-based lock checks";
686
687 TempFile tmp(make_temp_path());
688
689 {
690 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
691 EXPECT_TRUE(tree.insert(10));
692 EXPECT_TRUE(tree.insert(20));
693 tree.sync();
694 }
695
697 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Only);
698
699 const int shared_status = run_in_child([&]() -> int
700 {
701 try
702 {
704 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Only);
705 return child.range(0, 30).size() == 2 ? 0 : 2;
706 }
707 catch (...)
708 {
709 return 1;
710 }
711 });
715
716 const int writer_status = run_in_child([&]() -> int
717 {
718 try
719 {
721 tmp.path.string(), File_BPlus_Tree<int, Aleph::less<int>, 3>::Read_Write);
722 return 1;
723 }
724 catch (const std::runtime_error &)
725 {
726 return 0;
727 }
728 catch (...)
729 {
730 return 2;
731 }
732 });
736#else
737 GTEST_SKIP() << "fork-based lock validation is only available on Unix-like systems";
738#endif
739}
740
742{
743 TempFile tmp(make_temp_path());
744
745 {
746 std::ofstream out(tmp.path.string() + ".lock",
747 std::ios::binary | std::ios::trunc);
748 ASSERT_TRUE(out.good());
749 out << "stale\n";
750 }
751
752 {
753 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
754 EXPECT_TRUE(tree.insert(5));
755 tree.sync();
756 }
757
758 File_BPlus_Tree<int, Aleph::less<int>, 3> reopened(tmp.path.string(), false);
759 EXPECT_EQ(to_vector(reopened.range(0, 10)), (std::vector<int>{5}));
760}
761
763{
764 TempFile tmp(make_temp_path());
765
766 {
767 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
768 EXPECT_TRUE(tree.insert(1));
769 EXPECT_TRUE(tree.insert(2));
770 }
771
772 EXPECT_THROW((File_BPlus_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
773 std::runtime_error);
774}
775
777{
778 TempFile tmp(make_temp_path());
779
780 {
781 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
782 for (int value : {5, 10, 15, 20, 25, 30})
783 EXPECT_TRUE(tree.insert(value));
784 }
785
786 std::fstream io(tmp.path, std::ios::binary | std::ios::in | std::ios::out);
787 ASSERT_TRUE(io.good());
788 io.seekg(0, std::ios::end);
789 const auto end = io.tellg();
790 ASSERT_GT(end, 0);
791 io.seekg(end - std::streamoff(1));
792
793 char byte = 0;
794 io.read(&byte, 1);
795 ASSERT_TRUE(io.good());
796 byte ^= static_cast<char>(0x33);
797 io.seekp(end - std::streamoff(1));
798 io.write(&byte, 1);
799 ASSERT_TRUE(io.good());
800 io.close();
801
802 EXPECT_THROW((File_BPlus_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
803 std::runtime_error);
804}
805
807{
808 TempFile tmp(make_temp_path());
809 const auto journal = tmp.path.string() + ".journal";
810
811 {
812 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
813 for (int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
814 EXPECT_TRUE(tree.insert(value));
815 tree.sync();
816 }
817
818 std::error_code ec;
819 fs::copy_file(tmp.path, journal, fs::copy_options::overwrite_existing, ec);
820 ASSERT_FALSE(ec) << ec.message();
821
822 {
823 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
824 ASSERT_TRUE(out.good());
825 out << "corrupt";
826 }
827
829 EXPECT_TRUE(reopened.verify());
830 EXPECT_EQ(to_vector(reopened.range(0, 50)),
831 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45}));
832 EXPECT_FALSE(fs::exists(journal));
833}
834
836{
837 TempFile tmp(make_temp_path());
838 const auto wal = tmp.path.string() + ".wal";
839
840 {
841 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
842 for (int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
843 EXPECT_TRUE(tree.insert(value));
844 }
845
847
848 {
849 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
850 ASSERT_TRUE(out.good());
851 out << "corrupt";
852 }
853
855 EXPECT_TRUE(reopened.verify());
856 EXPECT_EQ(to_vector(reopened.range(0, 50)),
857 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45}));
858 EXPECT_FALSE(fs::exists(wal));
859}
860
862{
863 TempFile tmp(make_temp_path());
864 const auto wal = tmp.path.string() + ".wal";
865
866 {
867 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
868 for (int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
869 EXPECT_TRUE(tree.insert(value));
870 }
871
873
874 std::fstream wal_io(wal, std::ios::binary | std::ios::in | std::ios::out);
875 ASSERT_TRUE(wal_io.good());
876 wal_io.seekg(0, std::ios::end);
877 const auto wal_end = wal_io.tellg();
878 ASSERT_GT(wal_end, 0);
879 wal_io.seekg(wal_end - std::streamoff(1));
880
881 char byte = 0;
882 wal_io.read(&byte, 1);
883 ASSERT_TRUE(wal_io.good());
884 byte ^= static_cast<char>(0x11);
885 wal_io.seekp(wal_end - std::streamoff(1));
886 wal_io.write(&byte, 1);
887 ASSERT_TRUE(wal_io.good());
888 wal_io.close();
889
890 {
891 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
892 ASSERT_TRUE(out.good());
893 out << "corrupt";
894 }
895
896 EXPECT_THROW((File_BPlus_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
897 std::runtime_error);
898}
899
901{
902 constexpr size_t header_bytes =
903 Aleph::detail::Ordered_Tree_Snapshot_Magic_Size + sizeof(std::uint32_t) * 3
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);
909
910 TempFile tmp(make_temp_path());
911 const auto wal = tmp.path.string() + ".wal";
912 const auto old_snapshot = tmp.path.string() + ".old";
913 const auto new_snapshot = tmp.path.string() + ".new";
914
915 {
916 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
917 for (int value : {105, 110, 115, 120, 125, 130, 135, 140, 145, 150})
918 EXPECT_TRUE(tree.insert(value));
919 tree.sync();
920 }
921
922 std::error_code ec;
923 fs::copy_file(tmp.path, old_snapshot, fs::copy_options::overwrite_existing, ec);
924 ASSERT_FALSE(ec) << ec.message();
925
926 {
927 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
928 EXPECT_TRUE(tree.remove(120));
929 tree.sync();
930 }
931
932 fs::copy_file(tmp.path, new_snapshot, fs::copy_options::overwrite_existing, ec);
933 ASSERT_FALSE(ec) << ec.message();
935
938 ASSERT_GE(dirty_pages.size(), 2u);
939
940 fs::copy_file(old_snapshot, tmp.path, fs::copy_options::overwrite_existing, ec);
941 ASSERT_FALSE(ec) << ec.message();
942 copy_region(new_snapshot, tmp.path, 0, header_bytes);
944 static_cast<std::streamoff>(header_bytes
945 + (dirty_pages.front() - 1) * page_bytes),
946 page_bytes);
947
950 EXPECT_TRUE(reopened.verify());
952 EXPECT_FALSE(fs::exists(wal));
953}
954
956{
957 TempFile tmp(make_temp_path());
958 const auto wal = tmp.path.string() + ".wal";
959 const auto old_snapshot = tmp.path.string() + ".old";
960
961 {
962 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
963 for (int value : {25, 5, 35, 15, 45, 30, 10, 20, 40})
964 EXPECT_TRUE(tree.insert(value));
965 tree.sync();
966 }
967
968 std::error_code ec;
969 fs::copy_file(tmp.path, old_snapshot, fs::copy_options::overwrite_existing, ec);
970 ASSERT_FALSE(ec) << ec.message();
971
972 {
973 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
974 EXPECT_TRUE(tree.insert(50));
975 EXPECT_TRUE(tree.insert(55));
976 tree.sync();
977 }
978
980 fs::remove(old_snapshot, ec);
981
983 EXPECT_TRUE(reopened.verify());
984 EXPECT_EQ(to_vector(reopened.range(0, 60)),
985 (std::vector<int>{5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}));
986 EXPECT_FALSE(fs::exists(wal));
987}
988
990{
991 TempFile tmp(make_temp_path());
992
993 {
994 File_BPlus_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));
1009 EXPECT_EQ(to_vector(reopened.range(10, 20)),
1010 (std::vector<int>{10, 11, 12, 13, 14, 18, 19, 20}));
1011 EXPECT_FALSE(reopened.contains(1));
1012 EXPECT_FALSE(reopened.contains(64));
1013 EXPECT_TRUE(reopened.contains(66));
1014}
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
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.
#define TEST(name)
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
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
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:40
Page-managed persistent B-Tree with file-backed storage.
Page-managed persistent B+ Tree with file-backed storage.