Skip to content

Commit 5567c18

Browse files
authored
feat: add versioned database migration system (#1060)
* feat: add versioned database migration system Replace inline ALTER TABLE/CREATE INDEX blocks scattered across 29 Storage constructors with a centralised MigrationRunner that applies versioned SQL files from classpath manifests. Key capabilities: - Manifest-based migration discovery (db/<scope>/migrations.list) - Configurable table name placeholders (${tableName}) - MySQL/MariaDB advisory locking for concurrent server startups - Connection pinning to ensure lock/SQL/unlock on same connection - Fresh-install detection (skip SQL, mark latest version) - Existing-install baseline (mark V1 without re-running) - Idempotent DDL (individual statement failures logged, not fatal) - Separate local/global scopes with shared bm_schema_version table - ClassLoader parameter for addon JAR resource loading Also removes StorageUtils.convertIpColumn() (IPv6 migration). * fix: qualify migration scope with detection table name for multi-instance isolation Instances sharing the same database but using different table prefixes now track migrations independently (e.g. "local:bm_players" vs "local:bm_s2_players") instead of colliding on a bare "local" scope. * fix: use explicit instanceId config for multi-instance scope isolation Replaces the detection-table-name-derived scope with an opt-in instanceId config field. This avoids scope breakage when admins rename tables, while still supporting shared-database setups. * fix: close JDBC resources, make error tolerance opt-in per migration - Close CompiledStatement and DatabaseResults in finally blocks to prevent resource leaks under load - Add 'lenient' flag to manifest format; only lenient migrations continue on individual statement failures, strict migrations abort - Mark V1 baselines as lenient (idempotent DDL), future V2+ migrations default to strict - Use instanceScope consistently in all log messages - Remove unused SchemaVersion constructor * fix: remove instanceId comment from global database config Global databases are shared by design — all instances should use the same migration scope so migrations aren't applied redundantly.
1 parent 837f85c commit 5567c18

43 files changed

Lines changed: 732 additions & 485 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import me.confuser.banmanager.common.runnables.Runner;
1818
import me.confuser.banmanager.common.storage.*;
1919
import me.confuser.banmanager.common.storage.global.*;
20+
import me.confuser.banmanager.common.storage.migration.MigrationRunner;
2021
import me.confuser.banmanager.common.storage.mariadb.MariaDBDatabase;
2122
import me.confuser.banmanager.common.storage.mysql.MySQLDatabase;
2223
import me.confuser.banmanager.common.util.DriverManagerUtil;
@@ -174,6 +175,17 @@ public final void enable() throws Exception {
174175
throw new Exception("Unable to connect to database, ensure local is enabled in config and your connection details are correct");
175176
}
176177

178+
ClassLoader cl = MigrationRunner.class.getClassLoader();
179+
180+
MigrationRunner localMigrations = new MigrationRunner(
181+
this, localConn, config.getLocalDb(), "local", "players", cl);
182+
localMigrations.migrate();
183+
184+
if (globalConn != null) {
185+
MigrationRunner globalMigrations = new MigrationRunner(
186+
this, globalConn, config.getGlobalDb(), "global", "playerBans", cl);
187+
globalMigrations.migrate();
188+
}
177189

178190
setupStorage();
179191
} catch (SQLException e) {

common/src/main/java/me/confuser/banmanager/common/configs/DatabaseConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public abstract class DatabaseConfig {
4141
@Getter
4242
private int connectionTimeout;
4343
@Getter
44+
private String instanceId;
45+
@Getter
4446
private HashMap<String, DatabaseTableConfig<?>> tables = new HashMap<>();
4547

4648
private File dataFolder;
@@ -62,6 +64,7 @@ private DatabaseConfig(File dataFolder, ConfigurationSection conf) {
6264
verifyServerCertificate = conf.getBoolean("verifyServerCertificate", false);
6365
maxLifetime = conf.getInt("maxLifetime", 1800000);
6466
connectionTimeout = conf.getInt("connectionTimeout", 30000);
67+
instanceId = conf.getString("instanceId", "");
6568

6669
if (maxConnections > 30) maxConnections = 30;
6770
}

common/src/main/java/me/confuser/banmanager/common/storage/IpBanRecordStorage.java

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import me.confuser.banmanager.common.ormlite.support.ConnectionSource;
1414
import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig;
1515
import me.confuser.banmanager.common.ormlite.table.TableUtils;
16-
import me.confuser.banmanager.common.util.StorageUtils;
1716

1817
import java.sql.SQLException;
1918

@@ -30,28 +29,6 @@ public IpBanRecordStorage(BanManagerPlugin plugin) throws SQLException {
3029

3130
if (!this.isTableExists()) {
3231
TableUtils.createTable(connectionSource, tableConfig);
33-
} else {
34-
// Attempt to add new columns
35-
try {
36-
String update = "ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `createdReason` VARCHAR(255)";
37-
executeRawNoArgs(update);
38-
} catch (SQLException e) {
39-
}
40-
try {
41-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)");
42-
} catch (SQLException e) {
43-
}
44-
45-
try {
46-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName()
47-
+ " CHANGE `created` `created` BIGINT UNSIGNED,"
48-
+ " CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED,"
49-
+ " CHANGE `expired` `expired` BIGINT UNSIGNED"
50-
);
51-
} catch (SQLException e) {
52-
}
53-
54-
StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "ip");
5532
}
5633
}
5734

common/src/main/java/me/confuser/banmanager/common/storage/IpBanStorage.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig;
1919
import me.confuser.banmanager.common.ormlite.table.TableUtils;
2020
import me.confuser.banmanager.common.util.IPUtils;
21-
import me.confuser.banmanager.common.util.StorageUtils;
2221
import me.confuser.banmanager.common.util.TransactionHelper;
2322
import me.confuser.banmanager.common.util.UUIDUtils;
2423

@@ -39,22 +38,6 @@ public IpBanStorage(BanManagerPlugin plugin) throws SQLException {
3938

4039
if (!this.isTableExists()) {
4140
TableUtils.createTable(connectionSource, tableConfig);
42-
} else {
43-
StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "ip");
44-
45-
try {
46-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)");
47-
} catch (SQLException e) {
48-
}
49-
50-
try {
51-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName()
52-
+ " CHANGE `created` `created` BIGINT UNSIGNED,"
53-
+ " CHANGE `updated` `updated` BIGINT UNSIGNED,"
54-
+ " CHANGE `expires` `expires` BIGINT UNSIGNED"
55-
);
56-
} catch (SQLException e) {
57-
}
5841
}
5942

6043
loadAll();

common/src/main/java/me/confuser/banmanager/common/storage/IpMuteRecordStorage.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import me.confuser.banmanager.common.ormlite.support.ConnectionSource;
1313
import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig;
1414
import me.confuser.banmanager.common.ormlite.table.TableUtils;
15-
import me.confuser.banmanager.common.util.StorageUtils;
1615

1716
import java.sql.SQLException;
1817

@@ -29,22 +28,6 @@ public IpMuteRecordStorage(BanManagerPlugin plugin) throws SQLException {
2928

3029
if (!this.isTableExists()) {
3130
TableUtils.createTable(connectionSource, tableConfig);
32-
} else {
33-
StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "ip");
34-
35-
try {
36-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)");
37-
} catch (SQLException e) {
38-
}
39-
40-
try {
41-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName()
42-
+ " CHANGE `created` `created` BIGINT UNSIGNED,"
43-
+ " CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED,"
44-
+ " CHANGE `expired` `expired` BIGINT UNSIGNED"
45-
);
46-
} catch (SQLException e) {
47-
}
4831
}
4932
}
5033

common/src/main/java/me/confuser/banmanager/common/storage/IpMuteStorage.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig;
1818
import me.confuser.banmanager.common.ormlite.table.TableUtils;
1919
import me.confuser.banmanager.common.util.IPUtils;
20-
import me.confuser.banmanager.common.util.StorageUtils;
2120
import me.confuser.banmanager.common.util.TransactionHelper;
2221
import me.confuser.banmanager.common.util.UUIDUtils;
2322

@@ -36,22 +35,6 @@ public IpMuteStorage(BanManagerPlugin plugin) throws SQLException {
3635

3736
if (!this.isTableExists()) {
3837
TableUtils.createTable(connectionSource, tableConfig);
39-
} else {
40-
StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "ip");
41-
42-
try {
43-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)");
44-
} catch (SQLException e) {
45-
}
46-
47-
try {
48-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName()
49-
+ " CHANGE `created` `created` BIGINT UNSIGNED,"
50-
+ " CHANGE `updated` `updated` BIGINT UNSIGNED,"
51-
+ " CHANGE `expires` `expires` BIGINT UNSIGNED"
52-
);
53-
} catch (SQLException e) {
54-
}
5538
}
5639

5740
loadAll();

common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanRecordStorage.java

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import me.confuser.banmanager.common.ormlite.support.ConnectionSource;
1414
import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig;
1515
import me.confuser.banmanager.common.ormlite.table.TableUtils;
16-
import me.confuser.banmanager.common.util.StorageUtils;
1716

1817
import java.sql.SQLException;
1918

@@ -30,30 +29,6 @@ public IpRangeBanRecordStorage(BanManagerPlugin plugin) throws SQLException {
3029

3130
if (!this.isTableExists()) {
3231
TableUtils.createTable(connectionSource, tableConfig);
33-
} else {
34-
// Attempt to add new columns
35-
try {
36-
String update = "ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `createdReason` VARCHAR(255)";
37-
executeRawNoArgs(update);
38-
} catch (SQLException e) {
39-
}
40-
41-
StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "fromIp");
42-
StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "toIp");
43-
44-
try {
45-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)");
46-
} catch (SQLException e) {
47-
}
48-
49-
try {
50-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName()
51-
+ " CHANGE `created` `created` BIGINT UNSIGNED,"
52-
+ " CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED,"
53-
+ " CHANGE `expired` `expired` BIGINT UNSIGNED"
54-
);
55-
} catch (SQLException e) {
56-
}
5732
}
5833
}
5934

common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanStorage.java

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig;
2020
import me.confuser.banmanager.common.ormlite.table.TableUtils;
2121
import me.confuser.banmanager.common.util.IPUtils;
22-
import me.confuser.banmanager.common.util.StorageUtils;
2322
import me.confuser.banmanager.common.util.TransactionHelper;
2423
import me.confuser.banmanager.common.util.UUIDUtils;
2524

@@ -40,24 +39,6 @@ public IpRangeBanStorage(BanManagerPlugin plugin) throws SQLException {
4039

4140
if (!this.isTableExists()) {
4241
TableUtils.createTable(connectionSource, tableConfig);
43-
return;
44-
} else {
45-
StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "fromIp");
46-
StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "toIp");
47-
48-
try {
49-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)");
50-
} catch (SQLException e) {
51-
}
52-
53-
try {
54-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName()
55-
+ " CHANGE `created` `created` BIGINT UNSIGNED,"
56-
+ " CHANGE `updated` `updated` BIGINT UNSIGNED,"
57-
+ " CHANGE `expires` `expires` BIGINT UNSIGNED"
58-
);
59-
} catch (SQLException e) {
60-
}
6142
}
6243

6344
loadAll();

common/src/main/java/me/confuser/banmanager/common/storage/NameBanRecordStorage.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,6 @@ public NameBanRecordStorage(BanManagerPlugin plugin) throws SQLException {
2626

2727
if (!this.isTableExists()) {
2828
TableUtils.createTable(connectionSource, tableConfig);
29-
return;
30-
} else {
31-
try {
32-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName()
33-
+ " CHANGE `created` `created` BIGINT UNSIGNED,"
34-
+ " CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED,"
35-
+ " CHANGE `expired` `expired` BIGINT UNSIGNED"
36-
);
37-
} catch (SQLException e) {
38-
}
3929
}
4030
}
4131

common/src/main/java/me/confuser/banmanager/common/storage/NameBanStorage.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,6 @@ public NameBanStorage(BanManagerPlugin plugin) throws SQLException {
3232

3333
if (!this.isTableExists()) {
3434
TableUtils.createTable(connectionSource, tableConfig);
35-
return;
36-
} else {
37-
try {
38-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)");
39-
} catch (SQLException e) {
40-
}
41-
42-
try {
43-
executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName()
44-
+ " CHANGE `created` `created` BIGINT UNSIGNED,"
45-
+ " CHANGE `updated` `updated` BIGINT UNSIGNED,"
46-
+ " CHANGE `expires` `expires` BIGINT UNSIGNED"
47-
);
48-
} catch (SQLException e) {
49-
}
5035
}
5136

5237
loadAll();

0 commit comments

Comments
 (0)