diff --git a/src/database.cpp b/src/database.cpp index e69de29..647a22d 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -0,0 +1,231 @@ +#include "database.hpp" +#include +#include + +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; +} + +} // namespace simple_object_storage diff --git a/src/database.hpp b/src/database.hpp index aecfadf..ae2f000 100644 --- a/src/database.hpp +++ b/src/database.hpp @@ -17,6 +17,8 @@ class dbEntry { class Database { public: + static const int CURRENT_VERSION = 1; + Database(const std::filesystem::path& path); ~Database(); bool insert(const dbEntry& entry); @@ -27,6 +29,11 @@ class Database { private: std::filesystem::path path_; sqlite3* db_; + + bool createVersionTable(); + bool getVersion(int& version); + bool setVersion(int version); + bool migrate(int from_version, int to_version); }; } // namespace simple_object_storage