#include #include #include "database.hpp" #include "sqlite3/sqlite3.h" namespace simple_object_storage { bool Database::createVersionTable() { const char* sql = "CREATE TABLE IF NOT EXISTS version_info (" "version INTEGER NOT NULL" ");"; char* err_msg = nullptr; int rc = sqlite3_exec(db_, sql, nullptr, nullptr, &err_msg); if (rc != SQLITE_OK) { std::string error = err_msg; sqlite3_free(err_msg); return false; } // Check if we need to insert initial version sqlite3_stmt* stmt; sql = "SELECT COUNT(*) FROM version_info;"; if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) { return false; } bool needs_initial_version = false; if (sqlite3_step(stmt) == SQLITE_ROW) { needs_initial_version = sqlite3_column_int(stmt, 0) == 0; } sqlite3_finalize(stmt); if (needs_initial_version) { sql = "INSERT INTO version_info (version) VALUES (?);"; if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) { return false; } sqlite3_bind_int(stmt, 1, CURRENT_VERSION); bool success = sqlite3_step(stmt) == SQLITE_DONE; sqlite3_finalize(stmt); return success; } return true; } bool Database::getVersion(int& version) { const char* sql = "SELECT version FROM version_info;"; sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) { return false; } if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return false; } version = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); return true; } bool Database::setVersion(int version) { const char* sql = "UPDATE version_info SET version = ?;"; sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) { return false; } sqlite3_bind_int(stmt, 1, version); bool success = sqlite3_step(stmt) == SQLITE_DONE; sqlite3_finalize(stmt); return success; } bool Database::migrate(int from_version, int to_version) { // Currently only one version, so no migrations needed // This method will be expanded when we need to add new versions return true; } Database::Database(const std::filesystem::path& path) : path_(path) { int rc = sqlite3_open(path.string().c_str(), &db_); if (rc != SQLITE_OK) { throw std::runtime_error("Cannot open database: " + std::string(sqlite3_errmsg(db_))); } // Create version table and set initial version if (!createVersionTable()) { throw std::runtime_error("Failed to create version table"); } // Check current version and migrate if needed int current_version; if (!getVersion(current_version)) { throw std::runtime_error("Failed to get database version"); } if (current_version != CURRENT_VERSION) { if (!migrate(current_version, CURRENT_VERSION)) { throw std::runtime_error("Failed to migrate database"); } if (!setVersion(CURRENT_VERSION)) { throw std::runtime_error("Failed to update database version"); } } // Create objects table if it doesn't exist const char* create_table_sql = "CREATE TABLE IF NOT EXISTS objects (" "label_tag TEXT PRIMARY KEY," "hash TEXT NOT NULL," "metadata TEXT NOT NULL" ");"; char* err_msg = nullptr; rc = sqlite3_exec(db_, create_table_sql, nullptr, nullptr, &err_msg); if (rc != SQLITE_OK) { std::string error = err_msg; sqlite3_free(err_msg); throw std::runtime_error("Failed to create table: " + error); } } Database::~Database() { if (db_) { sqlite3_close(db_); } } bool Database::insert(const dbEntry& entry) { std::string sql = "INSERT INTO objects (label_tag, hash, metadata) VALUES (?, ?, ?);"; sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { return false; } sqlite3_bind_text(stmt, 1, entry.label_tag.c_str(), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, entry.hash.c_str(), -1, SQLITE_STATIC); std::string metadata_str = entry.metadata.dump(); sqlite3_bind_text(stmt, 3, metadata_str.c_str(), -1, SQLITE_STATIC); bool success = sqlite3_step(stmt) == SQLITE_DONE; sqlite3_finalize(stmt); return success; } bool Database::remove(const std::string& label_tag) { std::string sql = "DELETE FROM objects WHERE label_tag = ?;"; sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { return false; } sqlite3_bind_text(stmt, 1, label_tag.c_str(), -1, SQLITE_STATIC); bool success = sqlite3_step(stmt) == SQLITE_DONE; sqlite3_finalize(stmt); return success; } bool Database::get(const std::string& label_tag, dbEntry& entry) { std::string sql = "SELECT hash, metadata FROM objects WHERE label_tag = ?;"; sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { return false; } sqlite3_bind_text(stmt, 1, label_tag.c_str(), -1, SQLITE_STATIC); if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return false; } entry.label_tag = label_tag; entry.hash = reinterpret_cast(sqlite3_column_text(stmt, 0)); std::string metadata_str = reinterpret_cast(sqlite3_column_text(stmt, 1)); entry.metadata = nlohmann::json::parse(metadata_str); sqlite3_finalize(stmt); return true; } bool Database::update(const std::string& label_tag, const dbEntry& entry) { std::string sql = "UPDATE objects SET hash = ?, metadata = ? WHERE label_tag = ?;"; sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { return false; } sqlite3_bind_text(stmt, 1, entry.hash.c_str(), -1, SQLITE_STATIC); std::string metadata_str = entry.metadata.dump(); sqlite3_bind_text(stmt, 2, metadata_str.c_str(), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 3, label_tag.c_str(), -1, SQLITE_STATIC); bool success = sqlite3_step(stmt) == SQLITE_DONE; sqlite3_finalize(stmt); return success; } bool Database::list(std::vector& entries) { std::string sql = "SELECT label_tag, hash, metadata FROM objects;"; sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { return false; } entries.clear(); while (sqlite3_step(stmt) == SQLITE_ROW) { dbEntry entry; entry.label_tag = reinterpret_cast(sqlite3_column_text(stmt, 0)); entry.hash = reinterpret_cast(sqlite3_column_text(stmt, 1)); std::string metadata_str = reinterpret_cast(sqlite3_column_text(stmt, 2)); entry.metadata = nlohmann::json::parse(metadata_str); entries.push_back(entry); } sqlite3_finalize(stmt); return true; } bool Database::update_or_insert(const dbEntry& entry) { std::string sql = "INSERT OR REPLACE INTO objects (label_tag, hash, metadata) VALUES (?, ?, ?);"; sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { return false; } sqlite3_bind_text(stmt, 1, entry.label_tag.c_str(), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, entry.hash.c_str(), -1, SQLITE_STATIC); std::string metadata_str = entry.metadata.dump(); sqlite3_bind_text(stmt, 3, metadata_str.c_str(), -1, SQLITE_STATIC); bool success = sqlite3_step(stmt) == SQLITE_DONE; sqlite3_finalize(stmt); return success; } } // namespace simple_object_storage