252 lines
7.7 KiB
C++
252 lines
7.7 KiB
C++
#include <stdexcept>
|
|
#include <sstream>
|
|
|
|
#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<const char*>(sqlite3_column_text(stmt, 0));
|
|
std::string metadata_str = reinterpret_cast<const char*>(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<dbEntry>& 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<const char*>(sqlite3_column_text(stmt, 0));
|
|
entry.hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
|
|
std::string metadata_str = reinterpret_cast<const char*>(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
|