// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Verify() reports a heap handle pointing outside the committed heap and an // ordered column out of sequence, and is silent on a healthy file; the // type-erased DynamicRow reads any column by name without the struct. 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 Reading { std::string sensor; std::optional comment; double value; std::int64_t at; static constexpr std::uint32_t version = 1; static constexpr auto orderedBy = &Reading::at; }; void Patch(const fs::path& path, std::uint64_t offset, std::span bytes) { std::fstream file(path, std::ios::in | std::ios::out | std::ios::binary); file.seekp(static_cast(offset)); file.write(reinterpret_cast(bytes.data()), static_cast(bytes.size())); } } int main() { const fs::path dir = fs::temp_directory_path() / "crafter-db-verify"; fs::remove_all(dir); const fs::path path = dir / "r.cdb"; std::uint64_t rowsOffset = 0; { DB::Table table(path); table.Append({ "thermometer-kitchen", std::nullopt, 21.5, 100 }); table.Append({ "hygrometer", std::string("calibrated last week"), 40.0, 200 }); table.Append({ "barometer-outside-wall", std::nullopt, 1013.25, 300 }); table.Erase(1); const DB::VerifyResult healthy = table.Verify(); Check(healthy.Ok(), healthy.Ok() ? "healthy table verifies" : std::format("healthy table verifies: {}", healthy.problems[0])); const DB::DynamicRow dynamic = table.File().Dynamic(2); const std::optional sensor = dynamic.Find("sensor"); Check(sensor && dynamic.Text(*sensor) == "barometer-outside-wall", "DynamicRow text by name"); const std::optional value = dynamic.Find("value"); Check(value && dynamic.Scalar(*value) && std::get(*dynamic.Scalar(*value)) == 1013.25, "DynamicRow double by name"); const std::optional comment = dynamic.Find("comment"); Check(comment && !dynamic.Present(*comment), "DynamicRow sees a disengaged optional"); Check(table.File().Dynamic(1).Deleted() && table.File().Dynamic(1).Present(*comment) && table.File().Dynamic(1).Text(*comment) == "calibrated last week", "DynamicRow on a tombstoned row with an engaged optional"); const std::string described = dynamic.Describe(); Check(described.find("sensor=\"barometer-outside-wall\"") != std::string::npos && described.find("comment=null") != std::string::npos && described.find("at=300") != std::string::npos, std::format("Describe: {}", described)); Check(!dynamic.Find("nope").has_value(), "unknown column name"); table.Flush(); const DB::Inspection info = DB::TableFile::Inspect(path); rowsOffset = info.header.extents[0][0].fileOffset; } // Damage the heap offset of row 2's long sensor name (heap value). { const fs::path damaged = dir / "handle.cdb"; fs::copy_file(path, damaged); const DB::Inspection info = DB::TableFile::Inspect(damaged); std::uint16_t sensorOffset = 0; for (std::size_t i = 0; i < info.schema.columns.size(); i++) { if (info.schema.Name(i) == "sensor") sensorOffset = info.schema.columns[i].offset; } const std::uint64_t bogus = 1 << 30; Patch(damaged, rowsOffset + 2 * info.header.rowSize + sensorOffset + 8, std::as_bytes(std::span(&bogus, 1))); const DB::Table table(damaged, { .readOnly = true }); const DB::VerifyResult result = table.Verify(); Check(!result.Ok() && result.problems[0].find("row 2") != std::string::npos && result.problems[0].find("sensor") != std::string::npos, result.Ok() ? "damaged handle reported" : std::format("damaged handle reported: {}", result.problems[0])); bool threw = false; try { [[maybe_unused]] const std::string_view text = table[2][&Reading::sensor]; } catch (const DB::CorruptError&) { threw = true; } Check(threw, "reading through the damaged handle throws CorruptError instead of faulting"); Check(table[0][&Reading::sensor] == "thermometer-kitchen", "other rows still read"); } // Break the order of the ordered column. { const fs::path damaged = dir / "order.cdb"; fs::copy_file(path, damaged); const DB::Inspection info = DB::TableFile::Inspect(damaged); const std::int64_t early = 50; Patch(damaged, rowsOffset + 2 * info.header.rowSize + info.schema.columns[info.header.orderedColumn].offset, std::as_bytes(std::span(&early, 1))); const DB::Table table(damaged, { .readOnly = true }); const DB::VerifyResult result = table.Verify(); Check(!result.Ok() && result.problems[0].find("out of order") != std::string::npos, result.Ok() ? "order violation reported" : std::format("order violation reported: {}", result.problems[0])); } fs::remove_all(dir); if (failures == 0) std::println("ok"); return failures == 0 ? 0 : 1; }