// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Grows a table through many extents while holding Row views and // string_views taken before the growth. Guards the fixed-address mapping // promise: appends never move existing rows or heap bytes. import Crafter.DB; import std; using namespace Crafter; namespace fs = std::filesystem; using TimePoint = std::chrono::system_clock::time_point; namespace { int failures = 0; void Check(bool condition, std::string_view what) { if (!condition) { std::println("FAIL: {}", what); ++failures; } } template void CheckEqual(const T& actual, const T& expected, std::string_view what) { if (!(actual == expected)) { std::println("FAIL: {} — expected '{}', got '{}'", what, expected, actual); ++failures; } } struct Event { std::string label; std::string payload; std::uint64_t sequence; static constexpr std::uint32_t version = 1; static constexpr auto orderedBy = &Event::sequence; }; } int main() { const fs::path dir = fs::temp_directory_path() / "crafter-db-growth"; fs::remove_all(dir); const fs::path path = dir / "events.cdb"; constexpr std::uint64_t count = 300'000; { DB::Table table(path); table.Emplace(std::string_view("first"), std::string_view("payload of the very first row, long enough for the heap"), std::uint64_t(0)); const DB::Row first = table[0]; const std::string_view firstPayload = first[&Event::payload]; const std::byte* firstBytes = first.Bytes(); const char* firstHeapPointer = firstPayload.data(); std::vector batch; batch.reserve(1000); for (std::uint64_t i = 1; i < count; i++) { batch.push_back({ std::format("row-{}", i), std::format("payload-{}-{}", i, std::string(i % 40, 'p')), i }); if (batch.size() == 1000) { table.Append(batch); batch.clear(); } } if (!batch.empty()) table.Append(batch); CheckEqual(table.Size(), count, "all rows appended"); Check(first.Bytes() == firstBytes, "row pointer unchanged after growth"); Check(firstPayload.data() == firstHeapPointer, "heap pointer unchanged after growth"); Check(firstPayload == "payload of the very first row, long enough for the heap", "early string_view still reads correctly"); Check(first[&Event::label] == "first", "early row view still decodes"); Check(table[count - 1][&Event::label] == std::format("row-{}", count - 1), "last row readable"); Check(table[123456][&Event::payload] == std::format("payload-{}-{}", 123456, std::string(123456 % 40, 'p')), "middle row readable"); const DB::TableStats stats = table.Stats(); Check(stats.fileLength > count * DB::Layout::rowSize, "file holds the rows"); table.Flush(); const DB::Inspection info = DB::TableFile::Inspect(path); Check(info.commit.extentCount[0] >= 5, std::format("rows region grew through several extents ({})", info.commit.extentCount[0])); Check(info.commit.extentCount[1] >= 5, std::format("heap region grew through several extents ({})", info.commit.extentCount[1])); CheckEqual(info.commit.rowCount, count, "committed row count"); Check(info.commit.fileLength == fs::file_size(path), "committed length equals file size after flush"); } { const DB::Table table(path, { .readOnly = true }); CheckEqual(table.Size(), count, "rows survive reopen"); std::uint64_t expected = 0; bool ordered = true; for (DB::Row row : table) { if (row[&Event::sequence] != expected) ordered = false; expected++; } Check(ordered && expected == count, "sequence intact after reopen"); CheckEqual(table.LowerBound<&Event::sequence>(150'000), 150'000, "binary search over a multi-extent table"); } fs::remove_all(dir); if (failures == 0) std::println("ok"); return failures == 0 ? 0 : 1; }