reman3/tooling/database.cpp

256 lines
9.0 KiB
C++

#include "tool.hpp"
#include <stdexcept>
#include <spdlog/spdlog.h>
// Database classes
class PreparedStatements {
public:
sqlite3 *db;
sqlite3_stmt *delete_functions_stmt;
sqlite3_stmt *delete_imports_stmt;
sqlite3_stmt *insert_functions_stmt;
sqlite3_stmt *insert_imports_stmt;
sqlite3_stmt *delete_globals_stmt;
sqlite3_stmt *insert_globals_stmt;
void prepareStatement(const char *sql, sqlite3_stmt **stmt,
const std::string &error_msg);
PreparedStatements(sqlite3 *database) : db(database) {
prepareStatement("DELETE FROM Functions WHERE filepath = ?",
&delete_functions_stmt,
"Failed to prepare delete functions statement");
prepareStatement("DELETE FROM Imports WHERE filepath = ?",
&delete_imports_stmt,
"Failed to prepare delete imports statement");
prepareStatement("INSERT OR REPLACE INTO Functions (filepath, name, "
"address, type) VALUES (?, ?, ?, ?)",
&insert_functions_stmt,
"Failed to prepare insert functions statement");
prepareStatement("INSERT OR REPLACE INTO Imports (filepath, name, address, "
"type) VALUES (?, ?, ?, ?)",
&insert_imports_stmt,
"Failed to prepare insert imports statement");
prepareStatement("DELETE FROM Globals WHERE filepath = ?",
&delete_globals_stmt,
"Failed to prepare delete globals statement");
prepareStatement("INSERT OR REPLACE INTO Globals (filepath, name, address) "
"VALUES (?, ?, ?)",
&insert_globals_stmt,
"Failed to prepare insert globals statement");
}
~PreparedStatements() {
sqlite3_finalize(delete_functions_stmt);
sqlite3_finalize(delete_imports_stmt);
sqlite3_finalize(insert_functions_stmt);
sqlite3_finalize(insert_imports_stmt);
sqlite3_finalize(delete_globals_stmt);
sqlite3_finalize(insert_globals_stmt);
}
};
void PreparedStatements::prepareStatement(const char *sql, sqlite3_stmt **stmt,
const std::string &error_msg) {
if (sqlite3_prepare_v2(db, sql, -1, stmt, nullptr) != SQLITE_OK) {
throw std::runtime_error(error_msg + ": " + sqlite3_errmsg(db));
}
}
DatabaseManager::DatabaseManager(const std::string &db_path) : db(nullptr) {
if (sqlite3_open(db_path.c_str(), &db) != SQLITE_OK) {
spdlog::error("Can't open database: {}", sqlite3_errmsg(db));
sqlite3_close(db);
throw std::runtime_error("Failed to open database");
}
const char *create_tables = R"(
CREATE TABLE IF NOT EXISTS Functions (filepath TEXT, name TEXT, address TEXT, type INTEGER DEFAULT 0, PRIMARY KEY (name, filepath));
CREATE TABLE IF NOT EXISTS Imports (filepath TEXT, name TEXT, address TEXT, type INTEGER DEFAULT 0, PRIMARY KEY (name, filepath));
CREATE TABLE IF NOT EXISTS Globals (filepath TEXT, name TEXT, address TEXT);
)";
sqlite3_exec(db, create_tables, nullptr, nullptr, nullptr);
prepared_stmts = std::make_shared<PreparedStatements>(db);
}
DatabaseManager::~DatabaseManager() {
if (db)
sqlite3_close(db);
}
void DatabaseManager::clearEntriesForFile(const std::string &filepath) {
for (auto stmt : {prepared_stmts->delete_functions_stmt,
prepared_stmts->delete_imports_stmt}) {
sqlite3_reset(stmt);
sqlite3_bind_text(stmt, 1, filepath.c_str(), -1, SQLITE_STATIC);
sqlite3_step(stmt);
}
}
void DatabaseManager::clearGlobalsForFile(const std::string &filepath) {
sqlite3_reset(prepared_stmts->delete_globals_stmt);
sqlite3_bind_text(prepared_stmts->delete_globals_stmt, 1, filepath.c_str(),
-1, SQLITE_STATIC);
sqlite3_step(prepared_stmts->delete_globals_stmt);
}
void DatabaseManager::insertFunction(const FunctionInfo &func) {
sqlite3_stmt *stmt = func.is_import ? prepared_stmts->insert_imports_stmt
: prepared_stmts->insert_functions_stmt;
sqlite3_reset(stmt);
sqlite3_bind_text(stmt, 1, func.filepath.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, func.name.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, func.address.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 4, static_cast<int>(func.type));
sqlite3_step(stmt);
}
void DatabaseManager::insertGlobal(const GlobalInfo &global) {
sqlite3_reset(prepared_stmts->insert_globals_stmt);
sqlite3_bind_text(prepared_stmts->insert_globals_stmt, 1,
global.filepath.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_text(prepared_stmts->insert_globals_stmt, 2, global.name.c_str(),
-1, SQLITE_STATIC);
sqlite3_bind_text(prepared_stmts->insert_globals_stmt, 3,
global.address.c_str(), -1, SQLITE_STATIC);
sqlite3_step(prepared_stmts->insert_globals_stmt);
}
void DatabaseManager::beginTransaction() {
sqlite3_exec(db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr);
}
void DatabaseManager::commitTransaction() {
sqlite3_exec(db, "COMMIT", nullptr, nullptr, nullptr);
}
void DatabaseManager::rollbackTransaction() {
sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr);
}
bool DatabaseManager::checkDuplicateAddresses() {
const char *sql = R"(
WITH all_addresses AS (
SELECT 'Functions' as table_name, name, address, filepath FROM Functions WHERE address != '' AND type != 3
UNION ALL
SELECT 'Globals' as table_name, name, address, filepath FROM Globals WHERE address != ''
)
SELECT address, COUNT(*) as count,
GROUP_CONCAT(table_name || ':' || name || ' (' || filepath || ')', '; ') as entries
FROM all_addresses
GROUP BY address
HAVING COUNT(*) > 1
ORDER BY address;
)";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
spdlog::error("Failed to prepare duplicate address query: {}",
sqlite3_errmsg(db));
return false;
}
bool found_duplicates = false;
while (sqlite3_step(stmt) == SQLITE_ROW) {
found_duplicates = true;
const char *address = (const char *)sqlite3_column_text(stmt, 0);
int count = sqlite3_column_int(stmt, 1);
const char *entries = (const char *)sqlite3_column_text(stmt, 2);
spdlog::error("DUPLICATE ADDRESS: {} appears {} times in: {}", address,
count, entries);
}
sqlite3_finalize(stmt);
return found_duplicates;
}
bool DatabaseManager::checkDuplicateNames() {
bool found_duplicates = false;
// Check Functions table
const char *functions_sql = R"(
SELECT name, COUNT(*) as count,
GROUP_CONCAT(filepath, '; ') as filepaths
FROM Functions
WHERE type != 3
GROUP BY name
HAVING COUNT(*) > 1
ORDER BY name;
)";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, functions_sql, -1, &stmt, nullptr) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
found_duplicates = true;
const char *name = (const char *)sqlite3_column_text(stmt, 0);
int count = sqlite3_column_int(stmt, 1);
const char *filepaths = (const char *)sqlite3_column_text(stmt, 2);
spdlog::error(
"DUPLICATE FUNCTION NAME: '{}' appears {} times in files: {}", name,
count, filepaths);
}
sqlite3_finalize(stmt);
}
// Check Globals table
const char *globals_sql = R"(
SELECT name, COUNT(*) as count,
GROUP_CONCAT(filepath, '; ') as filepaths
FROM Globals
GROUP BY name
HAVING COUNT(*) > 1
ORDER BY name;
)";
if (sqlite3_prepare_v2(db, globals_sql, -1, &stmt, nullptr) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
found_duplicates = true;
const char *name = (const char *)sqlite3_column_text(stmt, 0);
int count = sqlite3_column_int(stmt, 1);
const char *filepaths = (const char *)sqlite3_column_text(stmt, 2);
spdlog::error("DUPLICATE GLOBAL NAME: '{}' appears {} times in files: {}",
name, count, filepaths);
}
sqlite3_finalize(stmt);
}
return found_duplicates;
}
std::vector<FunctionInfo> DatabaseManager::getFunctionsByType(FileType type) {
std::vector<FunctionInfo> functions;
const char *sql = R"(
SELECT name, address, filepath
FROM Functions
WHERE type = ? AND address != ''
ORDER BY address;
)";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
spdlog::error("Failed to prepare getFunctionsByType query: {}", sqlite3_errmsg(db));
return functions;
}
sqlite3_bind_int(stmt, 1, static_cast<int>(type));
while (sqlite3_step(stmt) == SQLITE_ROW) {
FunctionInfo func;
func.name = (const char *)sqlite3_column_text(stmt, 0);
func.address = (const char *)sqlite3_column_text(stmt, 1);
func.filepath = (const char *)sqlite3_column_text(stmt, 2);
func.type = type;
func.is_import = false; // Functions table contains non-imports
functions.push_back(func);
}
sqlite3_finalize(stmt);
return functions;
}