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>
97 lines
5.1 KiB
C++
97 lines
5.1 KiB
C++
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
// StringRef and BlobRef semantics: equality against string_view, Key,
|
|
// std::string, literals and other refs across the inline/heap boundary,
|
|
// prefix-equal non-matches, ordering, implicit conversion, and blob element
|
|
// alignment for 4- and 8-byte elements.
|
|
|
|
import Crafter.DB;
|
|
import std;
|
|
|
|
using namespace Crafter;
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace {
|
|
int failures = 0;
|
|
|
|
void Check(bool condition, std::string_view what) {
|
|
if (!condition) {
|
|
std::println("FAIL: {}", what);
|
|
++failures;
|
|
}
|
|
}
|
|
|
|
struct Strings {
|
|
std::string a;
|
|
std::string b;
|
|
std::vector<std::uint32_t> words;
|
|
std::vector<double> doubles;
|
|
std::vector<std::byte> bytes;
|
|
|
|
static constexpr std::uint32_t version = 1;
|
|
};
|
|
|
|
std::string_view V(std::string_view s) { return s; }
|
|
}
|
|
|
|
int main() {
|
|
const fs::path dir = fs::temp_directory_path() / "crafter-db-stringref";
|
|
fs::remove_all(dir);
|
|
DB::Table<Strings> table(dir / "s.cdb");
|
|
|
|
const std::vector<std::string> values = { "", "abc", "abcd", "abcde", "twelve_chars", "thirteen_char", "a much longer value that lives in the heap region" };
|
|
for (const std::string& v : values) table.Append({ v, v, { 1, 2, 3 }, { 1.5, 2.5 }, { std::byte{ 1 } } });
|
|
table.Append({ "abcd", "abcX", { 7, 8, 9, 10, 11 }, {}, {} }); // same length, same 3-byte prefix, differs in the 4th byte
|
|
table.Append({ "thirteen_char", "thirteen_chaX", {}, { 3.5 }, {} });
|
|
|
|
for (std::size_t i = 0; i < values.size(); i++) {
|
|
const DB::StringRef ref = table[i][&Strings::a];
|
|
const std::string& v = values[i];
|
|
Check(ref == V(v), std::format("== string_view for '{}'", v));
|
|
Check(ref == v, std::format("== std::string for '{}'", v));
|
|
Check(ref == DB::Key(v), std::format("== Key for '{}'", v));
|
|
Check(ref == table[i][&Strings::b], std::format("== StringRef for '{}'", v));
|
|
Check(!(ref != V(v)), std::format("!= false for '{}'", v));
|
|
Check(std::string_view(ref) == v, std::format("implicit string_view for '{}'", v));
|
|
Check(ref.Length() == v.size(), std::format("Length for '{}'", v));
|
|
Check(ref.Empty() == v.empty(), std::format("Empty for '{}'", v));
|
|
for (std::size_t j = 0; j < values.size(); j++) {
|
|
if (j == i) continue;
|
|
Check(!(ref == V(values[j])), std::format("'{}' != '{}'", v, values[j]));
|
|
Check(!(ref == DB::Key(values[j])), std::format("'{}' != Key '{}'", v, values[j]));
|
|
Check(!(ref == table[j][&Strings::a]), std::format("'{}' != ref '{}'", v, values[j]));
|
|
}
|
|
}
|
|
Check(!(table[7][&Strings::a] == table[7][&Strings::b]), "prefix-equal inline strings differ in the tail");
|
|
Check(!(table[8][&Strings::a] == table[8][&Strings::b]), "prefix-equal heap strings differ in the last byte");
|
|
Check(table[8][&Strings::a] == V("thirteen_char") && !(table[8][&Strings::a] == V("thirteen_chaX")), "heap compare checks all bytes");
|
|
Check((table[1][&Strings::a] <=> V("abd")) == std::strong_ordering::less, "ordering less");
|
|
Check((table[1][&Strings::a] <=> V("abb")) == std::strong_ordering::greater, "ordering greater");
|
|
Check((table[1][&Strings::a] <=> V("abc")) == std::strong_ordering::equal, "ordering equal");
|
|
Check(table[1][&Strings::a] < V("abcd"), "prefix shorter sorts first");
|
|
Check(table[6][&Strings::a] < table[1][&Strings::a], "ref <=> ref (space sorts before b)");
|
|
Check(table[1][&Strings::a] == "abc", "== literal");
|
|
|
|
const DB::BlobRef<std::uint32_t> words = table[0][&Strings::words];
|
|
const std::span<const std::uint32_t> wordSpan = words;
|
|
Check(wordSpan.size() == 3 && wordSpan[0] == 1 && wordSpan[2] == 3, "u32 blob round trip (inline)");
|
|
Check(reinterpret_cast<std::uintptr_t>(wordSpan.data()) % alignof(std::uint32_t) == 0, "inline u32 blob is 4-aligned");
|
|
const std::span<const std::uint32_t> longWords = table[7][&Strings::words];
|
|
Check(longWords.size() == 5 && longWords[4] == 11, "u32 blob round trip (heap)");
|
|
Check(reinterpret_cast<std::uintptr_t>(longWords.data()) % alignof(std::uint32_t) == 0, "heap u32 blob is 4-aligned");
|
|
const std::span<const double> doubles = table[0][&Strings::doubles];
|
|
Check(doubles.size() == 2 && doubles[1] == 2.5, "double blob round trip");
|
|
Check(reinterpret_cast<std::uintptr_t>(doubles.data()) % alignof(double) == 0, "double blob is 8-aligned (never inline)");
|
|
const std::span<const double> one = table[8][&Strings::doubles];
|
|
Check(one.size() == 1 && one[0] == 3.5, "single double blob");
|
|
const std::span<const std::byte> bytes = table[0][&Strings::bytes];
|
|
Check(bytes.size() == 1 && bytes[0] == std::byte{ 1 }, "byte blob round trip");
|
|
Check(table[7][&Strings::bytes].Size() == 0, "empty blob");
|
|
const Strings copy = table[7].Materialize();
|
|
Check(copy.words == std::vector<std::uint32_t>{ 7, 8, 9, 10, 11 } && copy.doubles.empty(), "Materialize blobs");
|
|
|
|
fs::remove_all(dir);
|
|
if (failures == 0) std::println("ok");
|
|
return failures == 0 ? 0 : 1;
|
|
}
|