diff --git a/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java b/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java index 7752190a3..3e1a6eb54 100644 --- a/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java +++ b/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java @@ -17,6 +17,7 @@ import me.confuser.banmanager.common.runnables.Runner; import me.confuser.banmanager.common.storage.*; import me.confuser.banmanager.common.storage.global.*; +import me.confuser.banmanager.common.storage.migration.MigrationRunner; import me.confuser.banmanager.common.storage.mariadb.MariaDBDatabase; import me.confuser.banmanager.common.storage.mysql.MySQLDatabase; import me.confuser.banmanager.common.util.DriverManagerUtil; @@ -174,6 +175,17 @@ public final void enable() throws Exception { throw new Exception("Unable to connect to database, ensure local is enabled in config and your connection details are correct"); } + ClassLoader cl = MigrationRunner.class.getClassLoader(); + + MigrationRunner localMigrations = new MigrationRunner( + this, localConn, config.getLocalDb(), "local", "players", cl); + localMigrations.migrate(); + + if (globalConn != null) { + MigrationRunner globalMigrations = new MigrationRunner( + this, globalConn, config.getGlobalDb(), "global", "playerBans", cl); + globalMigrations.migrate(); + } setupStorage(); } catch (SQLException e) { diff --git a/common/src/main/java/me/confuser/banmanager/common/configs/DatabaseConfig.java b/common/src/main/java/me/confuser/banmanager/common/configs/DatabaseConfig.java index 8a8d0f0a0..64069d97a 100644 --- a/common/src/main/java/me/confuser/banmanager/common/configs/DatabaseConfig.java +++ b/common/src/main/java/me/confuser/banmanager/common/configs/DatabaseConfig.java @@ -41,6 +41,8 @@ public abstract class DatabaseConfig { @Getter private int connectionTimeout; @Getter + private String instanceId; + @Getter private HashMap> tables = new HashMap<>(); private File dataFolder; @@ -62,6 +64,7 @@ private DatabaseConfig(File dataFolder, ConfigurationSection conf) { verifyServerCertificate = conf.getBoolean("verifyServerCertificate", false); maxLifetime = conf.getInt("maxLifetime", 1800000); connectionTimeout = conf.getInt("connectionTimeout", 30000); + instanceId = conf.getString("instanceId", ""); if (maxConnections > 30) maxConnections = 30; } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/IpBanRecordStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/IpBanRecordStorage.java index 66c97f300..86be9d7b8 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/IpBanRecordStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/IpBanRecordStorage.java @@ -13,7 +13,6 @@ import me.confuser.banmanager.common.ormlite.support.ConnectionSource; import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; -import me.confuser.banmanager.common.util.StorageUtils; import java.sql.SQLException; @@ -30,28 +29,6 @@ public IpBanRecordStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - // Attempt to add new columns - try { - String update = "ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `createdReason` VARCHAR(255)"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED," - + " CHANGE `expired` `expired` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } - - StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "ip"); } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/IpBanStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/IpBanStorage.java index ebc98c0de..3bf1888b4 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/IpBanStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/IpBanStorage.java @@ -18,7 +18,6 @@ import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; import me.confuser.banmanager.common.util.IPUtils; -import me.confuser.banmanager.common.util.StorageUtils; import me.confuser.banmanager.common.util.TransactionHelper; import me.confuser.banmanager.common.util.UUIDUtils; @@ -39,22 +38,6 @@ public IpBanStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "ip"); - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `updated` `updated` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } loadAll(); diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteRecordStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteRecordStorage.java index 32276a553..50b9a631f 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteRecordStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteRecordStorage.java @@ -12,7 +12,6 @@ import me.confuser.banmanager.common.ormlite.support.ConnectionSource; import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; -import me.confuser.banmanager.common.util.StorageUtils; import java.sql.SQLException; @@ -29,22 +28,6 @@ public IpMuteRecordStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "ip"); - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED," - + " CHANGE `expired` `expired` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteStorage.java index fb00264ff..00da52a7f 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteStorage.java @@ -17,7 +17,6 @@ import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; import me.confuser.banmanager.common.util.IPUtils; -import me.confuser.banmanager.common.util.StorageUtils; import me.confuser.banmanager.common.util.TransactionHelper; import me.confuser.banmanager.common.util.UUIDUtils; @@ -36,22 +35,6 @@ public IpMuteStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "ip"); - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `updated` `updated` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } loadAll(); diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanRecordStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanRecordStorage.java index e7a302f13..4ef5c9703 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanRecordStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanRecordStorage.java @@ -13,7 +13,6 @@ import me.confuser.banmanager.common.ormlite.support.ConnectionSource; import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; -import me.confuser.banmanager.common.util.StorageUtils; import java.sql.SQLException; @@ -30,30 +29,6 @@ public IpRangeBanRecordStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - // Attempt to add new columns - try { - String update = "ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `createdReason` VARCHAR(255)"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - - StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "fromIp"); - StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "toIp"); - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED," - + " CHANGE `expired` `expired` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanStorage.java index a9732c2b9..4acd2b3b7 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanStorage.java @@ -19,7 +19,6 @@ import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; import me.confuser.banmanager.common.util.IPUtils; -import me.confuser.banmanager.common.util.StorageUtils; import me.confuser.banmanager.common.util.TransactionHelper; import me.confuser.banmanager.common.util.UUIDUtils; @@ -40,24 +39,6 @@ public IpRangeBanStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - return; - } else { - StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "fromIp"); - StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "toIp"); - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `updated` `updated` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } loadAll(); diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/NameBanRecordStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/NameBanRecordStorage.java index 788f7d70d..d82a7108d 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/NameBanRecordStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/NameBanRecordStorage.java @@ -26,16 +26,6 @@ public NameBanRecordStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - return; - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED," - + " CHANGE `expired` `expired` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/NameBanStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/NameBanStorage.java index 62b8b7eb4..c107b7b39 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/NameBanStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/NameBanStorage.java @@ -32,21 +32,6 @@ public NameBanStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - return; - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `updated` `updated` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } loadAll(); diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanRecordStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanRecordStorage.java index 6cbaf3370..adf67d76a 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanRecordStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanRecordStorage.java @@ -29,26 +29,6 @@ public PlayerBanRecordStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - return; - } else { - // Attempt to add new columns - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `createdReason` VARCHAR(255)"); - } catch (SQLException e) { - } - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED," - + " CHANGE `expired` `expired` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanStorage.java index 138e0bb59..13f790aee 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanStorage.java @@ -37,21 +37,6 @@ public PlayerBanStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - return; - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `updated` `updated` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } loadAll(); diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerHistoryStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerHistoryStorage.java index e66e8b431..b463b8ece 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerHistoryStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerHistoryStorage.java @@ -11,7 +11,6 @@ import me.confuser.banmanager.common.ormlite.support.ConnectionSource; import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; -import me.confuser.banmanager.common.util.StorageUtils; import java.sql.SQLException; import java.util.ArrayList; @@ -37,35 +36,6 @@ public PlayerHistoryStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `join` `join` BIGINT UNSIGNED," - + " CHANGE `leave` `leave` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " MODIFY `ip` VARBINARY(16) NULL" - ); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " ADD COLUMN `name` VARCHAR(16) NOT NULL DEFAULT '' AFTER `player_id`" - ); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("CREATE INDEX idx_playerhistory_name ON " + tableConfig.getTableName() + " (name)"); - } catch (SQLException e) { - } - - StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "ip"); } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerKickStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerKickStorage.java index 08de526b3..6a071f3c8 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerKickStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerKickStorage.java @@ -24,11 +24,6 @@ public PlayerKickStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " CHANGE `created` `created` BIGINT UNSIGNED"); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteRecordStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteRecordStorage.java index e97fd74c1..97b396b6c 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteRecordStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteRecordStorage.java @@ -29,37 +29,6 @@ public PlayerMuteRecordStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - // Attempt to add new columns - try { - String update = "ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `createdReason` VARCHAR(255), " - + " ADD COLUMN `soft` TINYINT(1)," + - " ADD KEY `" + tableConfig.getTableName() + "_soft_idx` (`soft`)"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED," - + " CHANGE `expired` `expired` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `onlineOnly` TINYINT(1) NOT NULL DEFAULT 0"); - } catch (SQLException e) { - } - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `remainingOnlineTime` BIGINT UNSIGNED NOT NULL DEFAULT 0"); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteStorage.java index 0d670a863..e01d2455a 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteStorage.java @@ -34,43 +34,6 @@ public PlayerMuteStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - // Attempt to add new columns - try { - String update = "ALTER TABLE " + tableConfig - .getTableName() + " ADD COLUMN `soft` TINYINT(1)," + - " ADD KEY `" + tableConfig.getTableName() + "_soft_idx` (`soft`)"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - try { - String update = "ALTER TABLE " + tableConfig - .getTableName() + " ADD UNIQUE KEY `" + tableConfig.getTableName() + "_player_idx` (`player_id`)"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `silent` TINYINT(1)"); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `updated` `updated` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `onlineOnly` TINYINT(1) NOT NULL DEFAULT 0"); - } catch (SQLException e) { - } - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " ADD COLUMN `pausedRemaining` BIGINT UNSIGNED NOT NULL DEFAULT 0"); - } catch (SQLException e) { - } } loadAll(); diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerNoteStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerNoteStorage.java index a9ebd79a3..92c446fa1 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerNoteStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerNoteStorage.java @@ -27,11 +27,6 @@ public PlayerNoteStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " CHANGE `created` `created` BIGINT UNSIGNED"); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportCommandStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportCommandStorage.java index 9a133df7b..bb7037831 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportCommandStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportCommandStorage.java @@ -18,14 +18,6 @@ public PlayerReportCommandStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `updated` `updated` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportCommentStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportCommentStorage.java index 165650f0f..6987a1343 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportCommentStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportCommentStorage.java @@ -18,14 +18,6 @@ public PlayerReportCommentStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `updated` `updated` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportStorage.java index 103a40ecc..97b737dcd 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportStorage.java @@ -25,29 +25,6 @@ public PlayerReportStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - String update = "ALTER TABLE " + tableConfig - .getTableName() + " ADD COLUMN `state_id` INT(11) NOT NULL DEFAULT 1," + - " ADD COLUMN `assignee_id` BINARY(16)," + - " ADD KEY `" + tableConfig.getTableName() + "_state_id_idx` (`state_id`)," + - " ADD KEY `" + tableConfig.getTableName() + "_assignee_id_idx` (`assignee_id`)"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - try { - String update = "ALTER TABLE " + tableConfig.getTableName() + " MODIFY assignee_id BINARY(16) NULL"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `updated` `updated` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerStorage.java index f66ae2335..17d8e56eb 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerStorage.java @@ -16,7 +16,6 @@ import me.confuser.banmanager.common.ormlite.support.ConnectionSource; import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; -import me.confuser.banmanager.common.util.StorageUtils; import me.confuser.banmanager.common.util.UUIDProfile; import me.confuser.banmanager.common.util.UUIDUtils; @@ -42,13 +41,6 @@ public PlayerStorage(BanManagerPlugin plugin) throws SQLException { if (!isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " CHANGE `lastSeen` `lastSeen` BIGINT UNSIGNED"); - } catch (SQLException e) { - } - - StorageUtils.convertIpColumn(plugin, tableConfig.getTableName(), "ip", "bytes"); } setupConsole(); diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerWarnStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerWarnStorage.java index dd48345dc..5f6927d2d 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerWarnStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerWarnStorage.java @@ -45,35 +45,6 @@ public PlayerWarnStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - // Attempt to add new columns - try { - String update = "ALTER TABLE " + tableConfig - .getTableName() + " ADD COLUMN `expires` INT(10) NOT NULL DEFAULT 0," + - " ADD KEY `" + tableConfig.getTableName() + "_expires_idx` (`expires`)"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - try { - String update = "ALTER TABLE " + tableConfig - .getTableName() + " ADD COLUMN `points` INT(10) NOT NULL DEFAULT 1," + - " ADD KEY `" + tableConfig.getTableName() + "_points_idx` (`points`)"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - try { - String update = "ALTER TABLE " + tableConfig - .getTableName() + " MODIFY COLUMN `points` DECIMAL(60,2) NOT NULL DEFAULT 1"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/RollbackStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/RollbackStorage.java index 913245c75..1f7136567 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/RollbackStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/RollbackStorage.java @@ -23,14 +23,6 @@ public RollbackStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/conversion/AdvancedBan.java b/common/src/main/java/me/confuser/banmanager/common/storage/conversion/AdvancedBan.java index 26d6614dd..853142256 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/conversion/AdvancedBan.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/conversion/AdvancedBan.java @@ -195,7 +195,7 @@ public AdvancedBanConfig(String storageType, String host, int port, String name, boolean isEnabled, int maxConnections, int leakDetection, int maxLifetime, int connectionTimeout, HashMap> tables, File dataFolder) { super(storageType, host, port, name, user, password, useSSL, verifyServerCertificate, allowPublicKeyRetrieval, - isEnabled, maxConnections, leakDetection, maxLifetime, connectionTimeout, tables, dataFolder); + isEnabled, maxConnections, leakDetection, maxLifetime, connectionTimeout, "", tables, dataFolder); } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/conversion/H2.java b/common/src/main/java/me/confuser/banmanager/common/storage/conversion/H2.java index 27b9c5963..b847a6543 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/conversion/H2.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/conversion/H2.java @@ -600,7 +600,7 @@ public void importNameBans() { class H2Config extends DatabaseConfig { public H2Config(String storageType, String host, int port, String name, String user, String password, boolean useSSL, boolean verifyServerCertificate, boolean allowPublicKeyRetrieval, boolean isEnabled, int maxConnections, int leakDetection, int maxLifetime, int connectionTimeout, HashMap> tables, File dataFolder) { - super(storageType, host, port, name, user, password, useSSL, verifyServerCertificate, allowPublicKeyRetrieval, isEnabled, maxConnections, leakDetection, maxLifetime, connectionTimeout, tables, dataFolder); + super(storageType, host, port, name, user, password, useSSL, verifyServerCertificate, allowPublicKeyRetrieval, isEnabled, maxConnections, leakDetection, maxLifetime, connectionTimeout, "", tables, dataFolder); } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/conversion/LiteBans.java b/common/src/main/java/me/confuser/banmanager/common/storage/conversion/LiteBans.java index 9a096011b..8315d572f 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/conversion/LiteBans.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/conversion/LiteBans.java @@ -998,7 +998,7 @@ public LiteBansConfig(String storageType, String host, int port, String name, St boolean isEnabled, int maxConnections, int leakDetection, int maxLifetime, int connectionTimeout, HashMap> tables, File dataFolder) { super(storageType, host, port, name, user, password, useSSL, verifyServerCertificate, allowPublicKeyRetrieval, - isEnabled, maxConnections, leakDetection, maxLifetime, connectionTimeout, tables, dataFolder); + isEnabled, maxConnections, leakDetection, maxLifetime, connectionTimeout, "", tables, dataFolder); } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalIpBanRecordStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalIpBanRecordStorage.java index af0d222f0..f46db8457 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalIpBanRecordStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalIpBanRecordStorage.java @@ -24,11 +24,6 @@ public GlobalIpBanRecordStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " CHANGE `created` `created` BIGINT UNSIGNED"); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalIpBanStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalIpBanStorage.java index 925570680..d9aeadd77 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalIpBanStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalIpBanStorage.java @@ -23,14 +23,6 @@ public GlobalIpBanStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerBanRecordStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerBanRecordStorage.java index e527f4c34..aaca16829 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerBanRecordStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerBanRecordStorage.java @@ -24,11 +24,6 @@ public GlobalPlayerBanRecordStorage(BanManagerPlugin plugin) throws SQLException if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " CHANGE `created` `created` BIGINT UNSIGNED"); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerBanStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerBanStorage.java index 37b23eec4..0daf4243e 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerBanStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerBanStorage.java @@ -24,14 +24,6 @@ public GlobalPlayerBanStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerMuteRecordStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerMuteRecordStorage.java index 7c1685289..1348a69af 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerMuteRecordStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerMuteRecordStorage.java @@ -24,11 +24,6 @@ public GlobalPlayerMuteRecordStorage(BanManagerPlugin plugin) throws SQLExceptio if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " CHANGE `created` `created` BIGINT UNSIGNED"); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerMuteStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerMuteStorage.java index e2182b04e..4c97646c3 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerMuteStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerMuteStorage.java @@ -24,23 +24,6 @@ public GlobalPlayerMuteStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - // Attempt to add new columns - try { - String update = "ALTER TABLE " + tableConfig - .getTableName() + " ADD COLUMN `soft` TINYINT(1)," + - " ADD KEY `" + tableConfig.getTableName() + "_soft_idx` (`soft`)"; - executeRawNoArgs(update); - } catch (SQLException e) { - } - - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() - + " CHANGE `created` `created` BIGINT UNSIGNED," - + " CHANGE `expires` `expires` BIGINT UNSIGNED" - ); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerNoteStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerNoteStorage.java index 907985f53..a7c8160b8 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerNoteStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/global/GlobalPlayerNoteStorage.java @@ -24,11 +24,6 @@ public GlobalPlayerNoteStorage(BanManagerPlugin plugin) throws SQLException { if (!this.isTableExists()) { TableUtils.createTable(connectionSource, tableConfig); - } else { - try { - executeRawNoArgs("ALTER TABLE " + tableConfig.getTableName() + " CHANGE `created` `created` BIGINT UNSIGNED"); - } catch (SQLException e) { - } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/migration/MigrationRunner.java b/common/src/main/java/me/confuser/banmanager/common/storage/migration/MigrationRunner.java new file mode 100644 index 000000000..6b358fda0 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/storage/migration/MigrationRunner.java @@ -0,0 +1,400 @@ +package me.confuser.banmanager.common.storage.migration; + +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.configs.DatabaseConfig; +import me.confuser.banmanager.common.ormlite.field.SqlType; +import me.confuser.banmanager.common.ormlite.stmt.StatementBuilder; +import me.confuser.banmanager.common.ormlite.support.CompiledStatement; +import me.confuser.banmanager.common.ormlite.support.ConnectionSource; +import me.confuser.banmanager.common.ormlite.support.DatabaseConnection; +import me.confuser.banmanager.common.ormlite.support.DatabaseResults; +import me.confuser.banmanager.common.ormlite.table.TableUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MigrationRunner { + + private static final Pattern MANIFEST_PATTERN = Pattern.compile("^(V(\\d+)__(.+)\\.sql)(?:\\s+(\\S+))?$"); + static final String SCHEMA_TABLE = "bm_schema_version"; + + private final BanManagerPlugin plugin; + private final ConnectionSource connectionSource; + private final DatabaseConfig dbConfig; + private final String scope; + private final String detectionTableKey; + private final ClassLoader resourceLoader; + + private final String instanceScope; + + public MigrationRunner(BanManagerPlugin plugin, ConnectionSource connectionSource, + DatabaseConfig dbConfig, String scope, String detectionTableKey, + ClassLoader resourceLoader) { + this.plugin = plugin; + this.connectionSource = connectionSource; + this.dbConfig = dbConfig; + this.scope = scope; + this.detectionTableKey = detectionTableKey; + this.resourceLoader = resourceLoader; + + String id = dbConfig.getInstanceId(); + this.instanceScope = (id != null && !id.isEmpty()) ? scope + ":" + id : scope; + } + + public void migrate() throws SQLException { + List migrations = loadManifest(); + if (migrations.isEmpty()) { + plugin.getLogger().info("[Migration:" + instanceScope + "] No migrations found in manifest"); + return; + } + + String detectionTableName = dbConfig.getTable(detectionTableKey).getTableName(); + + int latestVersion = migrations.get(migrations.size() - 1).version; + boolean isH2 = dbConfig.getStorageType().equals("h2"); + + DatabaseConnection conn = connectionSource.getReadWriteConnection(""); + try { + if (!isH2) { + acquireAdvisoryLock(conn); + } + + try { + TableUtils.createTableIfNotExists(connectionSource, SchemaVersion.class); + + if (!tableExists(conn, detectionTableName)) { + plugin.getLogger().info("[Migration:" + instanceScope + "] Fresh install detected, marking schema at V" + latestVersion); + insertVersion(conn, latestVersion, "baseline (fresh install)"); + return; + } + + int currentVersion = getCurrentVersion(conn); + + if (currentVersion == 0) { + plugin.getLogger().info("[Migration:" + instanceScope + "] Existing install detected, marking V1 as baseline"); + insertVersion(conn, 1, "baseline (existing install)"); + currentVersion = 1; + } + + int applied = 0; + for (MigrationFile migration : migrations) { + if (migration.version <= currentVersion) { + continue; + } + + plugin.getLogger().info("[Migration:" + instanceScope + "] Applying V" + migration.version + " " + migration.description); + String sql = loadSqlFile(migration.filename); + if (sql.isEmpty()) { + throw new SQLException("[Migration:" + instanceScope + "] Migration file not found or empty: " + migration.filename); + } + sql = substitutePlaceholders(sql); + executeMigrationStatements(conn, sql, migration.lenient); + insertVersion(conn, migration.version, migration.description); + applied++; + } + + if (applied > 0) { + plugin.getLogger().info("[Migration:" + instanceScope + "] Applied " + applied + " migration(s)"); + } + } finally { + if (!isH2) { + releaseAdvisoryLock(conn); + } + } + } finally { + connectionSource.releaseConnection(conn); + } + } + + private void acquireAdvisoryLock(DatabaseConnection conn) throws SQLException { + CompiledStatement stmt = conn.compileStatement( + "SELECT GET_LOCK('bm_migration_" + instanceScope + "', 30)", + StatementBuilder.StatementType.SELECT, null, + DatabaseConnection.DEFAULT_RESULT_FLAGS, false); + try { + DatabaseResults results = stmt.runQuery(null); + try { + if (!results.next() || results.getInt(0) != 1) { + throw new SQLException("[Migration:" + instanceScope + "] Could not acquire advisory lock (another server may be migrating)"); + } + } finally { + closeQuietly(results); + } + } finally { + closeQuietly(stmt); + } + } + + private void releaseAdvisoryLock(DatabaseConnection conn) { + try { + conn.executeStatement("SELECT RELEASE_LOCK('bm_migration_" + instanceScope + "')", + DatabaseConnection.DEFAULT_RESULT_FLAGS); + } catch (SQLException e) { + plugin.getLogger().warning("[Migration:" + instanceScope + "] Failed to release advisory lock", e); + } + } + + private List loadManifest() { + List migrations = new ArrayList<>(); + String manifestPath = "db/" + scope + "/migrations.list"; + + try (InputStream is = resourceLoader.getResourceAsStream(manifestPath)) { + if (is == null) { + plugin.getLogger().warning("[Migration:" + instanceScope + "] No manifest found at " + manifestPath); + return migrations; + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + + Matcher matcher = MANIFEST_PATTERN.matcher(line); + if (matcher.matches()) { + String filename = matcher.group(1); + int version = Integer.parseInt(matcher.group(2)); + String description = matcher.group(3).replace('_', ' '); + boolean lenient = "lenient".equalsIgnoreCase(matcher.group(4)); + migrations.add(new MigrationFile(filename, version, description, lenient)); + } else { + plugin.getLogger().warning("[Migration:" + instanceScope + "] Skipping invalid manifest entry: " + line); + } + } + } + } catch (IOException e) { + plugin.getLogger().warning("[Migration:" + instanceScope + "] Failed to read manifest: " + e.getMessage()); + } + + migrations.sort(Comparator.comparingInt(m -> m.version)); + return migrations; + } + + private boolean tableExists(DatabaseConnection conn, String tableName) { + try { + conn.executeStatement("SELECT 1 FROM `" + tableName + "` LIMIT 1", + DatabaseConnection.DEFAULT_RESULT_FLAGS); + return true; + } catch (SQLException e) { + return false; + } + } + + private int getCurrentVersion(DatabaseConnection conn) throws SQLException { + try { + CompiledStatement stmt = conn.compileStatement( + "SELECT COALESCE(MAX(version), 0) FROM " + SCHEMA_TABLE + " WHERE scope = ?", + StatementBuilder.StatementType.SELECT, null, + DatabaseConnection.DEFAULT_RESULT_FLAGS, false); + try { + stmt.setObject(0, instanceScope, SqlType.STRING); + DatabaseResults results = stmt.runQuery(null); + try { + if (results.next()) { + return results.getInt(0); + } + } finally { + closeQuietly(results); + } + } finally { + closeQuietly(stmt); + } + } catch (SQLException e) { + // Table may not exist yet + } + return 0; + } + + private String loadSqlFile(String filename) { + String path = "db/" + scope + "/" + filename; + + try (InputStream is = resourceLoader.getResourceAsStream(path)) { + if (is == null) { + plugin.getLogger().warning("[Migration:" + instanceScope + "] SQL file not found: " + path); + return ""; + } + + byte[] bytes = readAllBytes(is); + return new String(bytes, StandardCharsets.UTF_8); + } catch (IOException e) { + plugin.getLogger().warning("[Migration:" + instanceScope + "] Failed to read SQL file: " + path); + return ""; + } + } + + private String substitutePlaceholders(String sql) { + for (Map.Entry> entry + : dbConfig.getTables().entrySet()) { + sql = sql.replace("${" + entry.getKey() + "}", entry.getValue().getTableName()); + } + return sql; + } + + private void executeMigrationStatements(DatabaseConnection conn, String sql, boolean lenient) throws SQLException { + List statements = splitStatements(sql); + + for (String statement : statements) { + try { + conn.executeStatement(statement, DatabaseConnection.DEFAULT_RESULT_FLAGS); + } catch (SQLException e) { + if (lenient) { + plugin.getLogger().warning("[Migration:" + instanceScope + "] Statement failed (continuing): " + e.getMessage()); + } else { + throw new SQLException("[Migration:" + instanceScope + "] Statement failed: " + e.getMessage(), e); + } + } + } + } + + static List splitStatements(String sql) { + List statements = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inSingleQuote = false; + boolean inDoubleQuote = false; + boolean inLineComment = false; + boolean inBlockComment = false; + + for (int i = 0; i < sql.length(); i++) { + char c = sql.charAt(i); + char next = (i + 1 < sql.length()) ? sql.charAt(i + 1) : '\0'; + + if (inLineComment) { + if (c == '\n') { + inLineComment = false; + current.append(c); + } + continue; + } + + if (inBlockComment) { + if (c == '*' && next == '/') { + inBlockComment = false; + i++; + } + continue; + } + + if (c == '-' && next == '-' && !inSingleQuote && !inDoubleQuote) { + inLineComment = true; + i++; + continue; + } + + if (c == '/' && next == '*' && !inSingleQuote && !inDoubleQuote) { + inBlockComment = true; + i++; + continue; + } + + if (c == '\\' && (inSingleQuote || inDoubleQuote)) { + current.append(c); + if (next != '\0') { + current.append(next); + i++; + } + continue; + } + + if (c == '\'' && !inDoubleQuote) { + inSingleQuote = !inSingleQuote; + } else if (c == '"' && !inSingleQuote) { + inDoubleQuote = !inDoubleQuote; + } + + if (c == ';' && !inSingleQuote && !inDoubleQuote) { + String stmt = current.toString().trim(); + if (!stmt.isEmpty()) { + statements.add(stmt); + } + current.setLength(0); + } else { + current.append(c); + } + } + + String remaining = current.toString().trim(); + if (!remaining.isEmpty()) { + statements.add(remaining); + } + + return statements; + } + + private void insertVersion(DatabaseConnection conn, int version, String description) throws SQLException { + long appliedAt = System.currentTimeMillis() / 1000L; + CompiledStatement stmt = conn.compileStatement( + "INSERT INTO " + SCHEMA_TABLE + " (version, description, appliedAt, scope) VALUES (?, ?, ?, ?)", + StatementBuilder.StatementType.UPDATE, null, + DatabaseConnection.DEFAULT_RESULT_FLAGS, false); + try { + stmt.setObject(0, version, SqlType.INTEGER); + stmt.setObject(1, description, SqlType.STRING); + stmt.setObject(2, appliedAt, SqlType.LONG); + stmt.setObject(3, instanceScope, SqlType.STRING); + stmt.runUpdate(); + } finally { + closeQuietly(stmt); + } + } + + private static void closeQuietly(CompiledStatement stmt) { + if (stmt != null) { + try { stmt.close(); } catch (IOException ignored) { } + } + } + + private static void closeQuietly(DatabaseResults results) { + if (results != null) { + try { results.close(); } catch (IOException ignored) { } + } + } + + private static byte[] readAllBytes(InputStream is) throws IOException { + byte[] buffer = new byte[4096]; + int bytesRead; + List chunks = new ArrayList<>(); + int totalLen = 0; + + while ((bytesRead = is.read(buffer)) != -1) { + byte[] chunk = new byte[bytesRead]; + System.arraycopy(buffer, 0, chunk, 0, bytesRead); + chunks.add(chunk); + totalLen += bytesRead; + } + + byte[] result = new byte[totalLen]; + int offset = 0; + for (byte[] chunk : chunks) { + System.arraycopy(chunk, 0, result, offset, chunk.length); + offset += chunk.length; + } + + return result; + } + + static class MigrationFile { + final String filename; + final int version; + final String description; + final boolean lenient; + + MigrationFile(String filename, int version, String description, boolean lenient) { + this.filename = filename; + this.version = version; + this.description = description; + this.lenient = lenient; + } + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/migration/SchemaVersion.java b/common/src/main/java/me/confuser/banmanager/common/storage/migration/SchemaVersion.java new file mode 100644 index 000000000..8d1622c9d --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/storage/migration/SchemaVersion.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.common.storage.migration; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import me.confuser.banmanager.common.ormlite.field.DatabaseField; +import me.confuser.banmanager.common.ormlite.table.DatabaseTable; + +@DatabaseTable(tableName = "bm_schema_version") +@NoArgsConstructor +public class SchemaVersion { + + @DatabaseField(generatedId = true) + @Getter + private int id; + + @DatabaseField(canBeNull = false) + @Getter + private int version; + + @DatabaseField(canBeNull = false, columnDefinition = "VARCHAR(255) NOT NULL") + @Getter + private String description; + + @DatabaseField(canBeNull = false, columnDefinition = "BIGINT NOT NULL") + @Getter + private long appliedAt; + + @DatabaseField(canBeNull = false, columnDefinition = "VARCHAR(50) NOT NULL") + @Getter + private String scope; +} diff --git a/common/src/main/java/me/confuser/banmanager/common/util/StorageUtils.java b/common/src/main/java/me/confuser/banmanager/common/util/StorageUtils.java index 9559d436b..7b42786cb 100644 --- a/common/src/main/java/me/confuser/banmanager/common/util/StorageUtils.java +++ b/common/src/main/java/me/confuser/banmanager/common/util/StorageUtils.java @@ -1,6 +1,5 @@ package me.confuser.banmanager.common.util; -import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.configs.DatabaseConfig; import me.confuser.banmanager.common.ormlite.dao.BaseDaoImpl; import me.confuser.banmanager.common.ormlite.field.SqlType; @@ -8,7 +7,6 @@ import me.confuser.banmanager.common.ormlite.support.CompiledStatement; import me.confuser.banmanager.common.ormlite.support.ConnectionSource; import me.confuser.banmanager.common.ormlite.support.DatabaseConnection; -import me.confuser.banmanager.common.ormlite.support.DatabaseResults; import java.io.IOException; import java.sql.SQLException; @@ -61,61 +59,4 @@ public static void updateTimestampsToDbTime(ConnectionSource connectionSource, D } } - public static void convertIpColumn(BanManagerPlugin plugin, String table, String column) { - convertIpColumn(plugin, table, column, "int"); - } - - public static void convertIpColumn(BanManagerPlugin plugin, String table, String column, String idType) { - try (DatabaseConnection connection = plugin.getLocalConn().getReadWriteConnection(table)) { - if (connection.update("ALTER TABLE `" + table + "` CHANGE COLUMN `" + column + "` `" + column + "` VARBINARY(16) NOT NULL", null, null) != 0) { - plugin.getLogger().info("Converting " + table + " " + column + " data to support IPv6"); - - plugin.getLogger().info("Attempting fast IPv6 conversion..."); - - try { - if (connection - .compileStatement("UPDATE `" + table + "` SET " + column + " = INET6_ATON(INET_NTOA(" + column + "))", StatementBuilder - .StatementType.UPDATE, null, DatabaseConnection.DEFAULT_RESULT_FLAGS, false) - .runUpdate() == 0) { - throw new SQLException("Failed to fast convert, attempting slow conversion..."); - } else { - plugin.getLogger().info("Successfully converted " + table + " " + column + " data to support IPv6"); - } - } catch (Exception e) { - plugin.getLogger().severe("Failed to fast convert due to " + e.getMessage() + ", attempting slow conversion..."); - - DatabaseResults results = connection - .compileStatement("SELECT `id`, INET_NTOA(HEX(UNHEX(CAST(" + column + " AS UNSIGNED)))) FROM `" + table + "`", StatementBuilder - .StatementType.SELECT, null, DatabaseConnection.DEFAULT_RESULT_FLAGS, false) - .runQuery(null); - - while (results.next()) { - CompiledStatement statement = connection - .compileStatement("UPDATE " + table + " SET `" + column + "` = ? WHERE `id` = ?", StatementBuilder - .StatementType.UPDATE, null, DatabaseConnection.DEFAULT_RESULT_FLAGS, false); - - Object id; - - if (idType.equals("int")) { - id = results.getInt(0); - } else { - id = results.getBytes(0); - } - - String ipStr = results.getString(1); - byte[] ip = IPUtils.toBytes(ipStr); - - statement.setObject(0, ip, SqlType.BYTE_ARRAY); - statement.setObject(1, id, idType.equals("int") ? SqlType.INTEGER : SqlType.BYTE_ARRAY); - - if (statement.runUpdate() == 0) { - plugin.getLogger().severe("Unable to convert " + ipStr + " in " + table + " for id " + id); - } - } - } - } - } catch (SQLException | IOException e) { - plugin.getLogger().warning("Failed to process storage operation", e); - } - } } diff --git a/common/src/main/resources/config.yml b/common/src/main/resources/config.yml index 335e7deb3..50f49665d 100644 --- a/common/src/main/resources/config.yml +++ b/common/src/main/resources/config.yml @@ -14,6 +14,8 @@ databases: verifyServerCertificate: false maxLifetime: 1800000 connectionTimeout: 30000 + # Set a unique id when multiple BanManager instances share the same database + # instanceId: '' tables: players: bm_players playerBans: bm_player_bans diff --git a/common/src/main/resources/db/global/V1__baseline.sql b/common/src/main/resources/db/global/V1__baseline.sql new file mode 100644 index 000000000..bf6a06d14 --- /dev/null +++ b/common/src/main/resources/db/global/V1__baseline.sql @@ -0,0 +1,11 @@ +-- Timestamp columns to BIGINT UNSIGNED +ALTER TABLE ${playerBans} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; +ALTER TABLE ${playerUnbans} CHANGE `created` `created` BIGINT UNSIGNED; +ALTER TABLE ${playerMutes} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; +ALTER TABLE ${playerUnmutes} CHANGE `created` `created` BIGINT UNSIGNED; +ALTER TABLE ${playerNotes} CHANGE `created` `created` BIGINT UNSIGNED; +ALTER TABLE ${ipBans} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; +ALTER TABLE ${ipUnbans} CHANGE `created` `created` BIGINT UNSIGNED; + +-- Soft mute (global mutes only) +ALTER TABLE ${playerMutes} ADD COLUMN `soft` TINYINT(1), ADD KEY `${playerMutes}_soft_idx` (`soft`); diff --git a/common/src/main/resources/db/global/migrations.list b/common/src/main/resources/db/global/migrations.list new file mode 100644 index 000000000..a43ed9c9a --- /dev/null +++ b/common/src/main/resources/db/global/migrations.list @@ -0,0 +1 @@ +V1__baseline.sql lenient diff --git a/common/src/main/resources/db/local/V1__baseline.sql b/common/src/main/resources/db/local/V1__baseline.sql new file mode 100644 index 000000000..84a484198 --- /dev/null +++ b/common/src/main/resources/db/local/V1__baseline.sql @@ -0,0 +1,67 @@ +-- Timestamp columns to BIGINT UNSIGNED +ALTER TABLE ${playerBans} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `updated` `updated` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; +ALTER TABLE ${playerBanRecords} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED, CHANGE `expired` `expired` BIGINT UNSIGNED; +ALTER TABLE ${playerMutes} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `updated` `updated` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; +ALTER TABLE ${playerMuteRecords} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED, CHANGE `expired` `expired` BIGINT UNSIGNED; +ALTER TABLE ${playerWarnings} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; +ALTER TABLE ${playerReports} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `updated` `updated` BIGINT UNSIGNED; +ALTER TABLE ${playerReportComments} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `updated` `updated` BIGINT UNSIGNED; +ALTER TABLE ${playerReportCommands} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `updated` `updated` BIGINT UNSIGNED; +ALTER TABLE ${playerKicks} CHANGE `created` `created` BIGINT UNSIGNED; +ALTER TABLE ${playerNotes} CHANGE `created` `created` BIGINT UNSIGNED; +ALTER TABLE ${players} CHANGE `lastSeen` `lastSeen` BIGINT UNSIGNED; +ALTER TABLE ${playerHistory} CHANGE `join` `join` BIGINT UNSIGNED, CHANGE `leave` `leave` BIGINT UNSIGNED; +ALTER TABLE ${ipBans} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `updated` `updated` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; +ALTER TABLE ${ipBanRecords} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED, CHANGE `expired` `expired` BIGINT UNSIGNED; +ALTER TABLE ${ipMutes} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `updated` `updated` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; +ALTER TABLE ${ipMuteRecords} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED, CHANGE `expired` `expired` BIGINT UNSIGNED; +ALTER TABLE ${ipRangeBans} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `updated` `updated` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; +ALTER TABLE ${ipRangeBanRecords} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED, CHANGE `expired` `expired` BIGINT UNSIGNED; +ALTER TABLE ${nameBans} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `updated` `updated` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; +ALTER TABLE ${nameBanRecords} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `pastCreated` `pastCreated` BIGINT UNSIGNED, CHANGE `expired` `expired` BIGINT UNSIGNED; +ALTER TABLE ${rollbacks} CHANGE `created` `created` BIGINT UNSIGNED, CHANGE `expires` `expires` BIGINT UNSIGNED; + +-- Silent column +ALTER TABLE ${playerBans} ADD COLUMN `silent` TINYINT(1); +ALTER TABLE ${playerBanRecords} ADD COLUMN `silent` TINYINT(1); +ALTER TABLE ${playerMutes} ADD COLUMN `silent` TINYINT(1); +ALTER TABLE ${playerMuteRecords} ADD COLUMN `silent` TINYINT(1); +ALTER TABLE ${ipBans} ADD COLUMN `silent` TINYINT(1); +ALTER TABLE ${ipBanRecords} ADD COLUMN `silent` TINYINT(1); +ALTER TABLE ${ipMutes} ADD COLUMN `silent` TINYINT(1); +ALTER TABLE ${ipMuteRecords} ADD COLUMN `silent` TINYINT(1); +ALTER TABLE ${ipRangeBans} ADD COLUMN `silent` TINYINT(1); +ALTER TABLE ${ipRangeBanRecords} ADD COLUMN `silent` TINYINT(1); +ALTER TABLE ${nameBans} ADD COLUMN `silent` TINYINT(1); + +-- Soft mute +ALTER TABLE ${playerMutes} ADD COLUMN `soft` TINYINT(1), ADD KEY `${playerMutes}_soft_idx` (`soft`); +ALTER TABLE ${playerMuteRecords} ADD COLUMN `createdReason` VARCHAR(255), ADD COLUMN `soft` TINYINT(1), ADD KEY `${playerMuteRecords}_soft_idx` (`soft`); + +-- Created reason +ALTER TABLE ${playerBanRecords} ADD COLUMN `createdReason` VARCHAR(255); +ALTER TABLE ${ipBanRecords} ADD COLUMN `createdReason` VARCHAR(255); +ALTER TABLE ${ipRangeBanRecords} ADD COLUMN `createdReason` VARCHAR(255); + +-- Mute unique key +ALTER TABLE ${playerMutes} ADD UNIQUE KEY `${playerMutes}_player_idx` (`player_id`); + +-- Warn points/expires +ALTER TABLE ${playerWarnings} ADD COLUMN `expires` INT(10) NOT NULL DEFAULT 0, ADD KEY `${playerWarnings}_expires_idx` (`expires`); +ALTER TABLE ${playerWarnings} ADD COLUMN `points` INT(10) NOT NULL DEFAULT 1, ADD KEY `${playerWarnings}_points_idx` (`points`); +ALTER TABLE ${playerWarnings} MODIFY COLUMN `points` DECIMAL(60,2) NOT NULL DEFAULT 1; + +-- Report workflow columns +ALTER TABLE ${playerReports} ADD COLUMN `state_id` INT(11) NOT NULL DEFAULT 1, ADD COLUMN `assignee_id` BINARY(16), ADD KEY `${playerReports}_state_id_idx` (`state_id`), ADD KEY `${playerReports}_assignee_id_idx` (`assignee_id`); +ALTER TABLE ${playerReports} MODIFY assignee_id BINARY(16) NULL; + +-- Online mute +ALTER TABLE ${playerMutes} ADD COLUMN `onlineOnly` TINYINT(1) NOT NULL DEFAULT 0; +ALTER TABLE ${playerMutes} ADD COLUMN `pausedRemaining` BIGINT UNSIGNED NOT NULL DEFAULT 0; +ALTER TABLE ${playerMuteRecords} ADD COLUMN `onlineOnly` TINYINT(1) NOT NULL DEFAULT 0; +ALTER TABLE ${playerMuteRecords} ADD COLUMN `remainingOnlineTime` BIGINT UNSIGNED NOT NULL DEFAULT 0; + +-- History +ALTER TABLE ${playerHistory} MODIFY `ip` VARBINARY(16) NULL; +ALTER TABLE ${playerHistory} ADD COLUMN `name` VARCHAR(16) NOT NULL DEFAULT '' AFTER `player_id`; +CREATE INDEX idx_playerhistory_name ON ${playerHistory} (name); diff --git a/common/src/main/resources/db/local/migrations.list b/common/src/main/resources/db/local/migrations.list new file mode 100644 index 000000000..a43ed9c9a --- /dev/null +++ b/common/src/main/resources/db/local/migrations.list @@ -0,0 +1 @@ +V1__baseline.sql lenient diff --git a/common/src/test/java/me/confuser/banmanager/common/storage/migration/MigrationIntegrationTest.java b/common/src/test/java/me/confuser/banmanager/common/storage/migration/MigrationIntegrationTest.java new file mode 100644 index 000000000..62aa4729e --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/storage/migration/MigrationIntegrationTest.java @@ -0,0 +1,81 @@ +package me.confuser.banmanager.common.storage.migration; + +import me.confuser.banmanager.common.*; +import me.confuser.banmanager.common.configs.PluginInfo; +import me.confuser.banmanager.common.ormlite.dao.Dao; +import me.confuser.banmanager.common.ormlite.dao.DaoManager; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.spy; + +public class MigrationIntegrationTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + private BanManagerPlugin plugin; + private TestServer server = spy(new TestServer()); + + @Before + public void setup() throws Exception { + CommonLogger logger = new TestLogger(); + PluginInfo pluginInfo = BasePluginTest.setupConfigs(temporaryFolder); + plugin = new BanManagerPlugin(pluginInfo, logger, temporaryFolder.getRoot(), new TestScheduler(), server, new TestMetrics()); + server.enable(plugin); + } + + @After + public void cleanup() { + if (plugin != null) { + plugin.disable(); + } + } + + @Test + public void freshInstall_marksLatestVersion() throws Exception { + plugin.enable(); + + Dao dao = DaoManager.createDao(plugin.getLocalConn(), SchemaVersion.class); + List versions = dao.queryForAll(); + + assertFalse("Schema versions should have been recorded", versions.isEmpty()); + + boolean hasLocal = false; + for (SchemaVersion sv : versions) { + if ("local".equals(sv.getScope())) { + hasLocal = true; + assertEquals("Fresh install should mark at latest version", 1, sv.getVersion()); + assertTrue("Description should indicate fresh install", sv.getDescription().contains("fresh install")); + } + } + + assertTrue("Should have a local scope version", hasLocal); + } + + @Test + public void secondEnable_isIdempotent() throws Exception { + plugin.enable(); + plugin.disable(); + + CommonLogger logger = new TestLogger(); + PluginInfo pluginInfo = BasePluginTest.setupConfigs(temporaryFolder); + plugin = new BanManagerPlugin(pluginInfo, logger, temporaryFolder.getRoot(), new TestScheduler(), server, new TestMetrics()); + server.enable(plugin); + plugin.enable(); + + Dao dao = DaoManager.createDao(plugin.getLocalConn(), SchemaVersion.class); + + String[] rawResults = dao.queryRaw( + "SELECT COUNT(*) FROM " + MigrationRunner.SCHEMA_TABLE + " WHERE scope = ?", "local" + ).getFirstResult(); + + int count = Integer.parseInt(rawResults[0]); + assertEquals("Should have exactly one local version row (no duplicates from second enable)", 1, count); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/storage/migration/MigrationRunnerTest.java b/common/src/test/java/me/confuser/banmanager/common/storage/migration/MigrationRunnerTest.java new file mode 100644 index 000000000..98e703726 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/storage/migration/MigrationRunnerTest.java @@ -0,0 +1,120 @@ +package me.confuser.banmanager.common.storage.migration; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + +public class MigrationRunnerTest { + + @Test + public void splitStatements_singleStatement() { + List result = MigrationRunner.splitStatements("ALTER TABLE foo ADD COLUMN bar INT;"); + assertEquals(1, result.size()); + assertEquals("ALTER TABLE foo ADD COLUMN bar INT", result.get(0)); + } + + @Test + public void splitStatements_multipleStatements() { + String sql = "ALTER TABLE foo ADD COLUMN bar INT;\nALTER TABLE baz DROP COLUMN qux;"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(2, result.size()); + assertEquals("ALTER TABLE foo ADD COLUMN bar INT", result.get(0)); + assertEquals("ALTER TABLE baz DROP COLUMN qux", result.get(1)); + } + + @Test + public void splitStatements_ignoreSemicolonInSingleQuotes() { + String sql = "UPDATE foo SET bar = 'hello;world';"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(1, result.size()); + assertEquals("UPDATE foo SET bar = 'hello;world'", result.get(0)); + } + + @Test + public void splitStatements_ignoreSemicolonInDoubleQuotes() { + String sql = "UPDATE foo SET bar = \"hello;world\";"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(1, result.size()); + assertEquals("UPDATE foo SET bar = \"hello;world\"", result.get(0)); + } + + @Test + public void splitStatements_skipLineComments() { + String sql = "-- This is a comment\nALTER TABLE foo ADD COLUMN bar INT;"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(1, result.size()); + assertEquals("ALTER TABLE foo ADD COLUMN bar INT", result.get(0)); + } + + @Test + public void splitStatements_skipBlockComments() { + String sql = "/* block comment */ALTER TABLE foo ADD COLUMN bar INT;"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(1, result.size()); + assertEquals("ALTER TABLE foo ADD COLUMN bar INT", result.get(0)); + } + + @Test + public void splitStatements_emptyInput() { + List result = MigrationRunner.splitStatements(""); + assertTrue(result.isEmpty()); + } + + @Test + public void splitStatements_onlyComments() { + String sql = "-- just a comment\n/* another comment */"; + List result = MigrationRunner.splitStatements(sql); + assertTrue(result.isEmpty()); + } + + @Test + public void splitStatements_noTrailingSemicolon() { + String sql = "ALTER TABLE foo ADD COLUMN bar INT"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(1, result.size()); + assertEquals("ALTER TABLE foo ADD COLUMN bar INT", result.get(0)); + } + + @Test + public void splitStatements_blankLinesBetween() { + String sql = "ALTER TABLE a ADD COLUMN b INT;\n\n\nALTER TABLE c ADD COLUMN d INT;"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(2, result.size()); + } + + @Test + public void splitStatements_multiLineStatement() { + String sql = "ALTER TABLE foo\n CHANGE `created` `created` BIGINT UNSIGNED,\n CHANGE `updated` `updated` BIGINT UNSIGNED;"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(1, result.size()); + assertTrue(result.get(0).contains("CHANGE `created`")); + assertTrue(result.get(0).contains("CHANGE `updated`")); + } + + @Test + public void splitStatements_escapedSingleQuote() { + String sql = "UPDATE foo SET bar = 'O\\'Brien';"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(1, result.size()); + assertEquals("UPDATE foo SET bar = 'O\\'Brien'", result.get(0)); + } + + @Test + public void splitStatements_escapedDoubleQuote() { + String sql = "UPDATE foo SET bar = \"say \\\"hello\\\"\";"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(1, result.size()); + assertEquals("UPDATE foo SET bar = \"say \\\"hello\\\"\"", result.get(0)); + } + + @Test + public void splitStatements_escapedSemicolonInQuotes() { + String sql = "UPDATE foo SET bar = 'test\\;value'; ALTER TABLE baz ADD x INT;"; + List result = MigrationRunner.splitStatements(sql); + assertEquals(2, result.size()); + assertEquals("UPDATE foo SET bar = 'test\\;value'", result.get(0)); + assertEquals("ALTER TABLE baz ADD x INT", result.get(1)); + } +}