256 lines
9.0 KiB
C++
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;
|
|
}
|