// SPDX-FileCopyrightText: 2023 Yury Gubich // SPDX-License-Identifier: GPL-3.0-or-later #include "mysql.h" #include #include #include "mysqld_error.h" #include "statement.h" constexpr const char* updateQuery = "UPDATE system SET `value` = ? WHERE `key` = 'version'"; static const std::filesystem::path buildSQLPath = "database"; struct ResDeleter { void operator () (MYSQL_RES* res) { mysql_free_result(res); } }; MySQL::MySQL(): DBInterface(Type::mysql), connection(), login(), password(), database() { mysql_init(&connection); } MySQL::~MySQL() { mysql_close(&connection); } void MySQL::connect(const std::string& path) { if (state != State::disconnected) return; MYSQL* con = &connection; MYSQL* res = mysql_real_connect( con, NULL, login.c_str(), password.c_str(), database.empty() ? NULL : database.c_str(), 0, path.c_str(), 0 ); if (res != con) throw std::runtime_error(std::string("Error changing connecting: ") + mysql_error(con)); state = State::connected; } void MySQL::setCredentials(const std::string& login, const std::string& password) { if (MySQL::login == login && MySQL::password == password) return; MySQL::login = login; MySQL::password = password; if (state == State::disconnected) return; MYSQL* con = &connection; int result = mysql_change_user( con, login.c_str(), password.c_str(), database.empty() ? NULL : database.c_str() ); if (result != 0) throw std::runtime_error(std::string("Error changing credetials: ") + mysql_error(con)); } void MySQL::setDatabase(const std::string& database) { if (MySQL::database == database) return; MySQL::database = database; if (state == State::disconnected) return; MYSQL* con = &connection; int result = mysql_select_db(con, database.c_str()); if (result != 0) throw std::runtime_error(std::string("Error changing db: ") + mysql_error(con)); } void MySQL::disconnect() { if (state == State::disconnected) return; MYSQL* con = &connection; mysql_close(con); mysql_init(con); //this is ridiculous! } void MySQL::executeFile(const std::filesystem::path& relativePath) { MYSQL* con = &connection; std::filesystem::path path = sharedPath() / relativePath; if (!std::filesystem::exists(path)) throw std::runtime_error("Error executing file " + std::filesystem::absolute(path).string() + ": file doesn't exist"); std::cout << "Executing file " << path << std::endl; std::ifstream inputFile(path); std::string query; while (std::getline(inputFile, query, ';')) { int result = mysql_query(con, query.c_str()); if (result != 0) { int errcode = mysql_errno(con); if (errcode == ER_EMPTY_QUERY) continue; throw std::runtime_error("Error executing file " + path.string() + ": " + mysql_error(con)); } } } uint8_t MySQL::getVersion() { MYSQL* con = &connection; int result = mysql_query(con, "SELECT value FROM system WHERE `key` = 'version'"); if (result != 0) { unsigned int errcode = mysql_errno(con); if (errcode == ER_NO_SUCH_TABLE) return 0; throw std::runtime_error(std::string("Error executing retreiving version: ") + mysql_error(con)); } std::unique_ptr res(mysql_store_result(con)); if (!res) throw std::runtime_error(std::string("Querying version returned no result: ") + mysql_error(con)); MYSQL_ROW row = mysql_fetch_row(res.get()); if (row) return std::stoi(row[0]); else return 0; } void MySQL::setVersion(uint8_t version) { std::string strVersion = std::to_string(version); Statement statement(&connection, updateQuery); statement.bind(strVersion.data(), MYSQL_TYPE_VAR_STRING); statement.execute(); } void MySQL::migrate(uint8_t targetVersion) { uint8_t currentVersion = getVersion(); while (currentVersion < targetVersion) { std::string fileName = "migrations/m" + std::to_string(currentVersion) + ".sql"; std::cout << "Performing migration " << std::to_string(currentVersion) << " -> " << std::to_string(++currentVersion) << std::endl; executeFile(fileName); setVersion(currentVersion); } std::cout << "Database is now on actual version " << std::to_string(targetVersion) << std::endl; }