12namespace fs = std::filesystem;
31 if (!std::isalpha(
static_cast<unsigned char>(name.front())) && name.front() !=
'_')
35 return std::all_of(name.begin(), name.end(), [](
unsigned char c) { return std::isalnum(c) || c ==
'_'; });
40 return "CREATE TABLE IF NOT EXISTS " +
table +
41 " (version INTEGER PRIMARY KEY, name TEXT NOT NULL, applied_at BIGINT NOT NULL)";
53 conn(
"INSERT INTO " +
table +
" (version, name, applied_at) VALUES (" + std::to_string(
migration.Version) +
", '" +
61 Log::Error(
"Invalid migration table name '{}'; refusing to run migrations.",
options.HistoryTable);
68 const Driver driver =
db.GetDriver();
74 return {.Success =
true};
80 if (!
entry.is_regular_file())
84 std::string name =
entry.path().filename().string();
85 if (!name.ends_with(
".sql"))
101 using Conn = std::remove_cvref_t<
decltype(
conn)>;
115 conn(
"DO GET_LOCK('voltmod_migrations_" +
lockKey +
"', 30)");
119 for (
const auto&
row :
conn(sqlpp::select(sqlpp::verbatim<sqlpp::integral>(
"MAX(version)").
as(sqlpp::alias::a))
122 current =
static_cast<int>(
row.a.value_or(0));
137 Log::Error(
"Migration {} ({}) unreadable: {}",
m.Version,
m.Name,
sql.error().Detail);
158 conn(
"BEGIN IMMEDIATE");
177 result.CurrentVersion =
m.Version;
178 Log::Info(
"Applied migration {} ({}).",
m.Version,
m.Name);
180 catch (
const std::exception&
e)
182 Log::Error(
"Migration {} ({}) failed: {}",
m.Version,
m.Name,
e.what());
190 conn(
"SELECT pg_advisory_unlock(" +
lockKey +
")");
194 conn(
"DO RELEASE_LOCK('voltmod_migrations_" +
lockKey +
"')");
199 Log::Info(
"Database schema up to date ({} migration(s) applied).",
result.Applied);
Async database access over Postgres, MariaDB and SQLite.
void Error(std::format_string< Args... > fmt, Args &&... args)
void Info(std::format_string< Args... > fmt, Args &&... args)
void Warn(std::format_string< Args... > fmt, Args &&... args)
static bool IsValidTableName(const std::string &name)
std::filesystem::path ResolvePath(std::string_view relativePath)
a relative path against the base directory. If the path is already absolute, returns it as-is.
std::optional< int > ParseMigrationVersion(std::string_view filename)
Result< std::string > ResolveDialect(std::string_view sql, Driver driver)
static std::string ReadFile(const std::filesystem::path &path)
static void ApplyMigration(Conn &conn, const std::string &table, const Migration &migration, const std::vector< std::string > &statements)
std::vector< std::string > SplitStatements(std::string_view sql)
Result< std::string > ReadAllText(std::string_view path)
Read path (resolved via ResolvePath) into a string.
MigrationResult RunMigrations(Database &db, std::string_view dir, const MigrationOptions &options={})
static std::string HistoryTableDdl(const std::string &table)