124 lines
3.1 KiB
C++
124 lines
3.1 KiB
C++
#include "repositories/MigrationRunner.h"
|
|
|
|
#include <drogon/drogon.h>
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace visione::repositories
|
|
{
|
|
namespace
|
|
{
|
|
std::string trim(std::string value)
|
|
{
|
|
auto notSpace = [](unsigned char ch) { return !std::isspace(ch); };
|
|
value.erase(value.begin(),
|
|
std::find_if(value.begin(), value.end(), notSpace));
|
|
value.erase(std::find_if(value.rbegin(), value.rend(), notSpace).base(),
|
|
value.end());
|
|
return value;
|
|
}
|
|
|
|
std::vector<std::string> splitStatements(const std::string &sql)
|
|
{
|
|
std::vector<std::string> statements;
|
|
std::string current;
|
|
bool inSingleQuote = false;
|
|
bool inDoubleQuote = false;
|
|
|
|
for (char ch : sql)
|
|
{
|
|
if (ch == '\'' && !inDoubleQuote)
|
|
{
|
|
inSingleQuote = !inSingleQuote;
|
|
}
|
|
else if (ch == '"' && !inSingleQuote)
|
|
{
|
|
inDoubleQuote = !inDoubleQuote;
|
|
}
|
|
|
|
if (ch == ';' && !inSingleQuote && !inDoubleQuote)
|
|
{
|
|
auto statement = trim(current);
|
|
if (!statement.empty())
|
|
{
|
|
statements.push_back(std::move(statement));
|
|
}
|
|
current.clear();
|
|
continue;
|
|
}
|
|
|
|
current.push_back(ch);
|
|
}
|
|
|
|
auto statement = trim(current);
|
|
if (!statement.empty())
|
|
{
|
|
statements.push_back(std::move(statement));
|
|
}
|
|
return statements;
|
|
}
|
|
|
|
bool hasMigration(const drogon::orm::DbClientPtr &db, const std::string &name)
|
|
{
|
|
const auto result = db->execSqlSync(
|
|
"SELECT name FROM schema_migrations WHERE name = ? LIMIT 1", name);
|
|
return !result.empty();
|
|
}
|
|
} // namespace
|
|
|
|
void MigrationRunner::run(const std::string &directory)
|
|
{
|
|
namespace fs = std::filesystem;
|
|
|
|
std::vector<fs::path> files;
|
|
if (!fs::exists(directory))
|
|
{
|
|
LOG_WARN << "Migration directory not found: " << directory;
|
|
return;
|
|
}
|
|
|
|
for (const auto &entry : fs::directory_iterator(directory))
|
|
{
|
|
if (entry.is_regular_file() && entry.path().extension() == ".sql")
|
|
{
|
|
files.push_back(entry.path());
|
|
}
|
|
}
|
|
std::sort(files.begin(), files.end());
|
|
|
|
auto db = drogon::app().getDbClient();
|
|
db->execSqlSync(
|
|
"CREATE TABLE IF NOT EXISTS schema_migrations ("
|
|
"name VARCHAR(255) NOT NULL PRIMARY KEY, "
|
|
"applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP"
|
|
")");
|
|
|
|
for (const auto &file : files)
|
|
{
|
|
const auto name = file.filename().string();
|
|
if (hasMigration(db, name))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
std::ifstream input(file);
|
|
std::stringstream buffer;
|
|
buffer << input.rdbuf();
|
|
|
|
for (const auto &statement : splitStatements(buffer.str()))
|
|
{
|
|
db->execSqlSync(statement);
|
|
}
|
|
db->execSqlSync("INSERT INTO schema_migrations (name) VALUES (?)", name);
|
|
LOG_INFO << "Applied migration " << name;
|
|
}
|
|
}
|
|
} // namespace visione::repositories
|