C++でコードベースのファイル名を使用したマイグレーションみたいなものを作った
コードベースのマイグレーションみたいなものを作りました。
例
以下に
- 1_Test.cpp
- 2_Hoge.cpp
がありますが、これのprefixの数字をidとして、カレントバージョンから最新バージョンまで全部実行する仕組みです
1_Test.cpp
#include"Migration.hpp" #include <iostream> namespace { MIGRATION(Test) { void up() const override { std::cout << "Up Test" << std::endl; } void down() const override { std::cout << "Down Test" << std::endl; } }_; }
2_Hoge.cpp
#include"Migration.hpp" #include <iostream> namespace { MIGRATION(Hoge) { void up() const override { std::cout << "Up Hoge" << std::endl; } void down() const override { std::cout << "Down Hoge" << std::endl; } }_; }
呼び出し
main.cpp
int main() { using Migration::MigrationHundler; int current = 0; std::cin >> current; std::cout << "Latest: " << current << std::endl; std::cout << "-- Migration Up --" << std::endl; current = MigrationHundler::Up(current); std::cout << "Latest: " << current << std::endl; std::cout << "-- Migration Down --" << std::endl; current = MigrationHundler::Down(current); std::cout << "Latest: " << current << std::endl; std::cout << "-- Migration Down --" << std::endl; current = MigrationHundler::Down(current); std::cout << "Latest: " << current << std::endl; }
出力
Latest: -1 -- Migration Up -- Up Test Up Hoge Latest: 2 -- Migration Down -- Down Hoge Latest: 1 -- Migration Down -- Down Test Latest: -1
実装
Migration.hpp
#pragma once #include <vector> #include <algorithm> #include <unordered_map> #include <optional> namespace Migration { struct IMigration { public: virtual ~IMigration() = default; virtual void up() const = 0; virtual void down() const = 0; }; template<int Id> struct Migration; class MigrationHundler { public: template<int Id> static void Regist(IMigration* migration) { m_migrations[Id] = migration; } static IMigration* Get(int version) { return m_migrations[version]; } static int Up(int currentVersion) { int nextVersion = -1; std::vector<int> updateVersions; for (auto&& m : m_migrations) { if (m.first > currentVersion) { updateVersions.push_back(m.first); if(nextVersion < m.first) { nextVersion = m.first; } } } std::ranges::sort(updateVersions); for(int v : updateVersions) { m_migrations[v]->up(); } return nextVersion == -1 ? currentVersion : nextVersion; } static int Down(int currentVersion) { if(m_migrations.find(currentVersion) != m_migrations.end()){ m_migrations[currentVersion]->down(); } int nextVersion = -1; for (auto&& m : m_migrations) { if (m.first < currentVersion) { if(nextVersion < m.first) { nextVersion = m.first; } } } return nextVersion; } private: MigrationHundler() = default; MigrationHundler(const MigrationHundler& other) = delete; void operator=(const MigrationHundler& other) = delete; private: inline static std::unordered_map<int, IMigration*> m_migrations; }; template<int Id> struct Migration : public IMigration { public: Migration() { MigrationHundler::Regist<Id>(this); } }; namespace detail { consteval int versionId(const char* path) { // filename取得 const char* filename = path; while (*path) { if (*path++ == '\\') { filename = path; } } // バージョン値取得 const char* str = filename; int value = 0; while (char c = *str) { if (c <= '9' && c >= '0') { value = (c - '0') + value * 10; } else { break; } ++str; } return value; } } } #define MIGRATION(name) struct name : ::Migration::Migration<::Migration::detail::versionId(__FILE__)>
ファイル名からprefixのidへの変換はdetail::versionId
でします。
これをMIGRATION
マクロに隠蔽することでよしなに紐づけてます。
Hundlerへの登録はインスタンス生成時にthisポインタを渡しますので、各実装をしたら一つどこかにインスタンスが必要です。
例では_
の変数名で無名名前空間に生成しています
実際の運用について
実際にはカレントバージョンをどこかに永続化する必要があるので何度も更新が走らないようにtxtなりdbなり自由に保存しておきましょう。