Initial commit: Crafter.DB

Header-only C++26 module library for typed, file-backed tables with
schema reflection, migrations, queries, and crash-safe commits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-17 04:16:51 +02:00
commit bfd0cc261b
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
34 changed files with 7506 additions and 0 deletions

View file

@ -0,0 +1,102 @@
// 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<typename T>
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<Event> 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<Event> first = table[0];
const std::string_view firstPayload = first[&Event::payload];
const std::byte* firstBytes = first.Bytes();
const char* firstHeapPointer = firstPayload.data();
std::vector<Event> 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<std::uint64_t>(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<Event>::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<std::uint64_t>(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<Event> table(path, { .readOnly = true });
CheckEqual<std::uint64_t>(table.Size(), count, "rows survive reopen");
std::uint64_t expected = 0;
bool ordered = true;
for (DB::Row<Event> row : table) {
if (row[&Event::sequence] != expected) ordered = false;
expected++;
}
Check(ordered && expected == count, "sequence intact after reopen");
CheckEqual<std::uint64_t>(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;
}