yams/tests/common/metadata_test_db.h
trvon c39ed84bf7
refactor(plugins)!: remove the symbol_extractor_v1 plugin ABI
Migration 40 dropped the symbol tables and graph edges, but the daemon
could still adopt an external symbol_extractor_v1 plugin (for example a
0.19 plugin left in a user plugin directory) and rebuild them.

Remove the interface header, the ABI adapter, plugin adoption, the
EntityGraphService extract-and-write path, the post-ingest symbol
channel, the write-coordinator symbol ops, and the KG-store symbol
metadata and extraction-state API. The ABI loader and the external host
now refuse a plugin that declares the interface (or the legacy
symbol_extraction capability) with a one-time warning. Status no longer
reports symbol extractors, generated configs no longer write
[plugins.symbol_extraction] (an existing section still parses), and
the benchmark suite drops symbol_extraction_bench.

Tests that exercised the dropped tables or the v39 schema template are
removed; the rest run against the current schema.

BREAKING CHANGE: code-symbol extraction is gone. Removed: the
symbol_extractor_v1 plugin ABI, `yams config grammar` (list, download,
path, auto-enable, auto-disable), the grammar steps in `yams init`, the
meson option `plugin-symbols`, the conan option
`enable_symbol_extraction`, and the Windows installer SymbolPlugin
component. Databases migrate to schema 41; a v0.19 binary refuses to
open them, and symbol data dropped by migration 40 cannot be recovered.
2026-09-22 13:30:35 -06:00

145 lines
5.6 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright 2025 YAMS Contributors
#pragma once
#include <chrono>
#include <filesystem>
#include <random>
#include <stdexcept>
#include <string>
#include <string_view>
#include "../../include/yams/metadata/database.h"
#include "../../include/yams/metadata/migration.h"
#include <yams/config/config_helpers.h>
namespace yams::test {
inline std::filesystem::path make_temp_sqlite_path(std::string_view prefix = "yams_test_") {
namespace fs = std::filesystem;
const auto environment = yams::config::getenv_nonempty("YAMS_TEST_TMPDIR");
const auto base = environment ? fs::path(*environment) : fs::temp_directory_path();
std::error_code ec;
fs::create_directories(base, ec);
std::uniform_int_distribution<int> dist(0, 9999);
thread_local std::mt19937_64 rng{std::random_device{}()};
for (int attempt = 0; attempt < 512; ++attempt) {
const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count();
auto candidate = base / (std::string(prefix) + std::to_string(stamp) + "_" +
std::to_string(dist(rng)) + ".db");
fs::remove(candidate, ec);
if (!fs::exists(candidate)) {
return candidate;
}
}
throw std::runtime_error("failed to allocate temporary sqlite path");
}
inline void remove_sqlite_artifacts(const std::filesystem::path& dbPath) {
std::error_code ec;
std::filesystem::remove(dbPath, ec);
std::filesystem::remove(std::filesystem::path(dbPath.string() + "-wal"), ec);
std::filesystem::remove(std::filesystem::path(dbPath.string() + "-shm"), ec);
}
class MetadataDbTemplate {
public:
explicit MetadataDbTemplate(std::string_view prefix = "yams_metadata_template_",
std::optional<int> targetVersion = std::nullopt)
: templatePath_(make_temp_sqlite_path(prefix)) {
metadata::Database db;
auto openResult = db.open(templatePath_.string(), metadata::ConnectionMode::Create);
if (!openResult) {
throw std::runtime_error("failed to open metadata template db: " +
openResult.error().message);
}
metadata::MigrationManager manager(db);
auto initResult = manager.initialize();
if (!initResult) {
db.close();
throw std::runtime_error("failed to initialize metadata template migrations: " +
initResult.error().message);
}
manager.registerMigrations(metadata::YamsMetadataMigrations::getAllMigrations());
auto migrateResult =
targetVersion.has_value() ? manager.migrateTo(*targetVersion) : manager.migrate();
if (!migrateResult) {
db.close();
throw std::runtime_error("failed to migrate metadata template db: " +
migrateResult.error().message);
}
db.close();
}
~MetadataDbTemplate() { remove_sqlite_artifacts(templatePath_); }
MetadataDbTemplate(const MetadataDbTemplate&) = delete;
MetadataDbTemplate& operator=(const MetadataDbTemplate&) = delete;
[[nodiscard]] std::filesystem::path clone(std::string_view prefix) const {
auto target = make_temp_sqlite_path(prefix);
// VACUUM INTO produces a self-contained single-file copy, checkpointing any WAL in the
// template. Raw copy_file of the main file plus -wal/-shm sidecars is incorrect: the
// -shm is a derived shared-memory index that must be rebuilt on open, and a stale -shm
// corrupts WAL replay on Windows.
metadata::Database db;
auto openResult = db.open(templatePath_.string(), metadata::ConnectionMode::ReadWrite);
if (!openResult) {
throw std::runtime_error("failed to open metadata template db for clone: " +
openResult.error().message);
}
auto vacuumResult =
db.execute("VACUUM INTO '" + escapeSqlStringLiteral(target.string()) + "'");
db.close();
if (!vacuumResult) {
throw std::runtime_error("failed to clone metadata template db: " +
vacuumResult.error().message);
}
verifyCloneIntegrity(target);
return target;
}
private:
static std::string escapeSqlStringLiteral(const std::string& raw) {
std::string escaped;
escaped.reserve(raw.size() + 2);
for (const char c : raw) {
escaped.push_back(c);
if (c == '\'') {
escaped.push_back('\'');
}
}
return escaped;
}
// Fail loudly if the clone is missing the KG schema, so a broken clone surfaces here with a
// clear message instead of an opaque "no such table" later in a test.
void verifyCloneIntegrity(const std::filesystem::path& target) const {
metadata::Database db;
auto openResult = db.open(target.string(), metadata::ConnectionMode::ReadWrite);
if (!openResult) {
throw std::runtime_error("failed to open cloned metadata db: " +
openResult.error().message);
}
auto probeResult = db.execute("SELECT 1 FROM kg_nodes LIMIT 0");
db.close();
if (!probeResult) {
throw std::runtime_error("cloned metadata db is missing the KG schema: " +
probeResult.error().message);
}
}
std::filesystem::path templatePath_;
};
inline const MetadataDbTemplate& migrated_metadata_db_template() {
static const MetadataDbTemplate templateDb{};
return templateDb;
}
} // namespace yams::test