C++ 数据库迁移:模式管理与版本控制
在当今使用关系型数据库(PostgreSQL、MySQL、SQLite)的 C++ 项目中,支持模式演进是一项必备实践。迁移允许自动应用表结构、索引和约束的变更,从而在应用程序版本之间保持数据完整性。与动态语言不同,C++ 没有内置的迁移工具,但有多个成熟的库可以解决此问题。
C++ 迁移库的基本要求
- 版本控制:每次迁移必须具有唯一标识符(数字或时间戳)。
- 回滚支持:能够应用“向上”(创建/修改)和“向下”(删除/回滚)操作。
- 数据库集成:通过标准驱动程序(libpq、sqlite3、MySQL Connector)工作。
- 事务性:每次迁移在单独的事务中执行,以确保原子性。
- 自动应用:提供命令行工具或 API,以便在应用程序启动时运行。
流行的 C++ 迁移工具
1. cpp-migrations(推荐)
一个轻量级的开源库,支持 PostgreSQL、MySQL、SQLite。迁移以 C++ 类的形式描述,包含 up() 和 down() 方法。版本记录在 migrations.json 文件中。
// cpp-migrations 的迁移示例#include <migration.hpp>
class CreateUsersTable : public Migration {public: void up() override { execute("CREATE TABLE users (" "id SERIAL PRIMARY KEY," "name VARCHAR(100) NOT NULL," "email VARCHAR(255) UNIQUE," "created_at TIMESTAMP DEFAULT NOW()" ")"); }
void down() override { execute("DROP TABLE IF EXISTS users"); }};
REGISTER_MIGRATION(CreateUsersTable, "001_create_users");通过 API 运行迁移:
#include <migration_runner.hpp>
int main() { MigrationRunner runner("postgresql://user:pass@localhost/db"); runner.applyPending(); // 应用所有未应用的迁移 return 0;}2. 基于 libpq 的迁移(自定义方法)
如果项目已在使用 libpq(PostgreSQL 原生客户端),可以基于 SQL 文件实现一个简单的迁移管理器。结构示例:
migrations/├── 001_create_users.sql├── 001_create_users.down.sql├── 002_add_role_column.sql└── 002_add_role_column.down.sqlC++ 加载器代码:
#include <pqxx/pqxx>#include <filesystem>#include <fstream>
void applyMigrations(pqxx::connection &conn) { pqxx::work txn(conn); // 创建用于跟踪已应用迁移的表 txn.exec("CREATE TABLE IF NOT EXISTS _migrations (" "id SERIAL PRIMARY KEY," "name TEXT UNIQUE," "applied_at TIMESTAMP DEFAULT NOW())"); for (const auto &entry : std::filesystem::directory_iterator("migrations")) { std::string filename = entry.path().filename().string(); if (filename.find(".down.") != std::string::npos) continue; if (filename.find(".sql") == std::string::npos) continue; // 检查迁移是否已应用 auto check = txn.exec_params("SELECT 1 FROM _migrations WHERE name = $1", filename); if (check.empty()) { std::ifstream file(entry.path()); std::string sql((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); txn.exec(sql); txn.exec_params("INSERT INTO _migrations (name) VALU