Crafter.DB/tests/ShouldCompareStringRefs/main.cpp
Jorijn van der Graaf bfd0cc261b
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>
2026-10-17 04:16:51 +02:00

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;
}