Skip to content

Commit 9285821

Browse files
committed
db(migrations): implement schema diff-based makemigrations tool
- Add MakeMigrations pipeline (schema diff → up/down SQL) - Introduce JSON schema loading and snapshot handling - Generate deterministic, timestamped migration files - Wire migrator CLI with DB drivers (MySQL supported) - Expose migration, diff, sql and schema APIs publicly - Make migrator tool buildable via VIX_DB_BUILD_TOOLS
1 parent c0564bd commit 9285821

14 files changed

Lines changed: 908 additions & 35 deletions

File tree

CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ include(GNUInstallDirs)
1818
# ------------------------------------------------------------------------------
1919
option(VIX_DB_BUILD_TESTS "Build unit tests for Vix DB" OFF)
2020
option(VIX_DB_BUILD_EXAMPLES "Build examples for Vix DB" OFF)
21-
option(VIX_DB_BUILD_TOOLS "Build DB CLI tools (migrator)" OFF)
21+
if (DEFINED VIX_UMBRELLA_BUILD)
22+
set(VIX_DB_BUILD_TOOLS ON CACHE BOOL "Build DB CLI tools (migrator)" FORCE)
23+
endif()
2224

2325
# SQL engines (drivers)
2426
option(VIX_DB_USE_MYSQL "Enable MySQL Connector/C++ driver" ON)
@@ -222,6 +224,9 @@ set(VIX_DB_SOURCES
222224
src/mig/MigrationsRunner.cpp
223225
src/mig/FileMigrationsRunner.cpp
224226
src/Sha256.cpp
227+
src/schema/Json.cpp
228+
src/mig/diff/Diff.cpp
229+
src/mig/sql/MySqlGenerator.cpp
225230
)
226231

227232
# MySQL driver sources
@@ -261,6 +266,7 @@ if (VIX_DB_BUILD_TOOLS)
261266
target_sources(vix_db_migrator PRIVATE
262267
${CMAKE_CURRENT_SOURCE_DIR}/tools/migrator/main.cpp
263268
${CMAKE_CURRENT_SOURCE_DIR}/tools/migrator/MigratorCLI.cpp
269+
${CMAKE_CURRENT_SOURCE_DIR}/tools/migrator/MakeMigrations.cpp
264270
)
265271

266272
target_link_libraries(vix_db_migrator PRIVATE vix::db)

include/vix/db/mig/diff/Diff.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#ifndef VIX_DB_MIG_DIFF_DIFF_HPP
2+
#define VIX_DB_MIG_DIFF_DIFF_HPP
3+
4+
#include <vix/db/mig/diff/Op.hpp>
5+
#include <vix/db/schema/Schema.hpp>
6+
7+
#include <vector>
8+
9+
namespace vix::db::mig::diff
10+
{
11+
// MVP: only supports create/drop table, add/drop column, create/drop index
12+
std::vector<Op> diff_or_throw(const vix::db::schema::Schema &from,
13+
const vix::db::schema::Schema &to);
14+
15+
} // namespace vix::db::mig::diff
16+
17+
#endif

include/vix/db/mig/diff/Op.hpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#ifndef VIX_DB_MIG_DIFF_OP_HPP
2+
#define VIX_DB_MIG_DIFF_OP_HPP
3+
4+
#include <vix/db/schema/Schema.hpp>
5+
6+
#include <string>
7+
#include <variant>
8+
#include <vector>
9+
10+
namespace vix::db::mig::diff
11+
{
12+
using vix::db::schema::Column;
13+
using vix::db::schema::Index;
14+
using vix::db::schema::Table;
15+
16+
struct CreateTable
17+
{
18+
Table table;
19+
};
20+
21+
struct DropTable
22+
{
23+
Table table; // keep full table for down
24+
};
25+
26+
struct AddColumn
27+
{
28+
std::string table;
29+
Column column;
30+
};
31+
32+
struct DropColumn
33+
{
34+
std::string table;
35+
Column column; // keep full def so down can re-add
36+
};
37+
38+
struct CreateIndex
39+
{
40+
std::string table;
41+
Index index;
42+
};
43+
44+
struct DropIndex
45+
{
46+
std::string table;
47+
Index index; // keep def for down (recreate)
48+
};
49+
50+
using Op = std::variant<CreateTable, DropTable, AddColumn, DropColumn, CreateIndex, DropIndex>;
51+
52+
} // namespace vix::db::mig::diff
53+
54+
#endif
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef VIX_DB_MIG_SQL_MYSQL_GENERATOR_HPP
2+
#define VIX_DB_MIG_SQL_MYSQL_GENERATOR_HPP
3+
4+
#include <vix/db/mig/diff/Op.hpp>
5+
6+
#include <string>
7+
#include <vector>
8+
9+
namespace vix::db::mig::sql
10+
{
11+
std::string to_mysql_up(const std::vector<vix::db::mig::diff::Op> &ops);
12+
std::string to_mysql_down(const std::vector<vix::db::mig::diff::Op> &ops);
13+
14+
} // namespace vix::db::mig::sql
15+
16+
#endif

include/vix/db/schema/Json.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef VIX_DB_SCHEMA_JSON_HPP
2+
#define VIX_DB_SCHEMA_JSON_HPP
3+
4+
#include <vix/db/schema/Schema.hpp>
5+
6+
#include <string>
7+
8+
namespace vix::db::schema
9+
{
10+
// Serialize/deserialize schema snapshot (nlohmann/json)
11+
std::string to_json_string(const Schema &s, bool pretty = true);
12+
Schema from_json_string_or_throw(const std::string &json);
13+
14+
} // namespace vix::db::schema
15+
16+
#endif

include/vix/db/schema/Schema.hpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#ifndef VIX_DB_SCHEMA_SCHEMA_HPP
2+
#define VIX_DB_SCHEMA_SCHEMA_HPP
3+
4+
#include <vix/db/schema/Types.hpp>
5+
6+
#include <cstdint>
7+
#include <optional>
8+
#include <string>
9+
#include <utility>
10+
#include <vector>
11+
12+
namespace vix::db::schema
13+
{
14+
struct Column
15+
{
16+
std::string name;
17+
Type type;
18+
19+
bool nullable = true;
20+
bool primary_key = false;
21+
bool auto_increment = false;
22+
bool unique = false;
23+
24+
std::optional<DefaultValue> def;
25+
};
26+
27+
struct Index
28+
{
29+
std::string name;
30+
std::vector<std::string> columns;
31+
bool unique = false;
32+
};
33+
34+
struct Table
35+
{
36+
std::string name;
37+
std::vector<Column> columns;
38+
std::vector<Index> indexes;
39+
40+
const Column *findColumn(const std::string &n) const
41+
{
42+
for (const auto &c : columns)
43+
if (c.name == n)
44+
return &c;
45+
return nullptr;
46+
}
47+
48+
Column *findColumn(const std::string &n)
49+
{
50+
for (auto &c : columns)
51+
if (c.name == n)
52+
return &c;
53+
return nullptr;
54+
}
55+
56+
const Index *findIndex(const std::string &n) const
57+
{
58+
for (const auto &i : indexes)
59+
if (i.name == n)
60+
return &i;
61+
return nullptr;
62+
}
63+
};
64+
65+
struct Schema
66+
{
67+
std::vector<Table> tables;
68+
69+
const Table *findTable(const std::string &n) const
70+
{
71+
for (const auto &t : tables)
72+
if (t.name == n)
73+
return &t;
74+
return nullptr;
75+
}
76+
77+
Table *findTable(const std::string &n)
78+
{
79+
for (auto &t : tables)
80+
if (t.name == n)
81+
return &t;
82+
return nullptr;
83+
}
84+
};
85+
86+
} // namespace vix::db::schema
87+
88+
#endif

include/vix/db/schema/Types.hpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#ifndef VIX_DB_SCHEMA_TYPES_HPP
2+
#define VIX_DB_SCHEMA_TYPES_HPP
3+
4+
#include <cstdint>
5+
#include <optional>
6+
#include <string>
7+
#include <utility>
8+
#include <vector>
9+
10+
namespace vix::db::schema
11+
{
12+
enum class Dialect
13+
{
14+
MySQL,
15+
SQLite
16+
};
17+
18+
enum class BaseType
19+
{
20+
Int,
21+
BigInt,
22+
Double,
23+
Bool,
24+
VarChar,
25+
Text,
26+
DateTime
27+
};
28+
29+
struct Type
30+
{
31+
BaseType base{};
32+
std::optional<std::uint32_t> size; // for VARCHAR(n)
33+
34+
static Type Int() { return {BaseType::Int, std::nullopt}; }
35+
static Type BigInt() { return {BaseType::BigInt, std::nullopt}; }
36+
static Type Double() { return {BaseType::Double, std::nullopt}; }
37+
static Type Bool() { return {BaseType::Bool, std::nullopt}; }
38+
static Type Text() { return {BaseType::Text, std::nullopt}; }
39+
static Type DateTime() { return {BaseType::DateTime, std::nullopt}; }
40+
41+
static Type VarChar(std::uint32_t n) { return {BaseType::VarChar, n}; }
42+
43+
bool operator==(const Type &o) const { return base == o.base && size == o.size; }
44+
bool operator!=(const Type &o) const { return !(*this == o); }
45+
};
46+
47+
struct DefaultValue
48+
{
49+
// Keep as raw SQL literal for MVP ("0", "'text'", "CURRENT_TIMESTAMP")
50+
std::string sql_literal;
51+
};
52+
53+
} // namespace vix::db::schema
54+
55+
#endif

src/mig/diff/Diff.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#include <vix/db/mig/diff/Diff.hpp>
2+
3+
#include <stdexcept>
4+
#include <unordered_map>
5+
6+
namespace vix::db::mig::diff
7+
{
8+
using vix::db::schema::Schema;
9+
using vix::db::schema::Table;
10+
11+
static std::unordered_map<std::string, const Table *> map_tables(const Schema &s)
12+
{
13+
std::unordered_map<std::string, const Table *> m;
14+
m.reserve(s.tables.size());
15+
for (const auto &t : s.tables)
16+
m.emplace(t.name, &t);
17+
return m;
18+
}
19+
20+
std::vector<Op> diff_or_throw(const Schema &from, const Schema &to)
21+
{
22+
std::vector<Op> ops;
23+
24+
auto A = map_tables(from);
25+
auto B = map_tables(to);
26+
27+
// 1) Drop tables missing in 'to'
28+
for (const auto &[name, ta] : A)
29+
{
30+
if (!B.count(name))
31+
ops.push_back(DropTable{*ta});
32+
}
33+
34+
// 2) Create tables new in 'to'
35+
for (const auto &[name, tb] : B)
36+
{
37+
if (!A.count(name))
38+
{
39+
ops.push_back(CreateTable{*tb});
40+
continue;
41+
}
42+
43+
// 3) Same table: diff columns + indexes
44+
const auto *oldT = A.at(name);
45+
const auto *newT = tb;
46+
47+
// Columns: drops
48+
for (const auto &c_old : oldT->columns)
49+
{
50+
if (!newT->findColumn(c_old.name))
51+
ops.push_back(DropColumn{name, c_old});
52+
}
53+
54+
// Columns: adds
55+
for (const auto &c_new : newT->columns)
56+
{
57+
if (!oldT->findColumn(c_new.name))
58+
ops.push_back(AddColumn{name, c_new});
59+
}
60+
61+
// Indexes: drops
62+
for (const auto &i_old : oldT->indexes)
63+
{
64+
if (!newT->findIndex(i_old.name))
65+
ops.push_back(DropIndex{name, i_old});
66+
}
67+
68+
// Indexes: adds
69+
for (const auto &i_new : newT->indexes)
70+
{
71+
if (!oldT->findIndex(i_new.name))
72+
ops.push_back(CreateIndex{name, i_new});
73+
}
74+
}
75+
76+
return ops;
77+
}
78+
79+
} // namespace vix::db::mig::diff

0 commit comments

Comments
 (0)