Add version for database for better future updates

This commit is contained in:
Eric 2019-06-16 15:29:18 +02:00
parent 3730eb9703
commit e3dadb5896
3 changed files with 285 additions and 156 deletions

View File

@ -36,12 +36,14 @@ import java.util.UUID;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
public abstract class Database { public abstract class Database {
private static final int DATABASE_VERSION = 2;
private static Set<String> notFoundWorlds = new HashSet<>(); private static Set<String> notFoundWorlds = new HashSet<>();
private String tableShops; String tableShops;
private String tableLogs; String tableLogs;
private String tableLogouts; String tableLogouts;
String tableFields;
ShopChest plugin; ShopChest plugin;
HikariDataSource dataSource; HikariDataSource dataSource;
@ -52,9 +54,157 @@ public abstract class Database {
abstract HikariDataSource getDataSource(); abstract HikariDataSource getDataSource();
abstract String getQueryCreateTableShops();
abstract String getQueryCreateTableLog();
abstract String getQueryCreateTableLogout();
abstract String getQueryCreateTableFields();
abstract String getQueryGetTable();
private void update() throws SQLException {
String queryGetTable = getQueryGetTable();
String queryUpdateVersion = "REPLACE INTO " + tableFields + " VALUES ('version', ?)";
try (Connection con = dataSource.getConnection()) {
boolean needsUpdate1 = false; // update "shop_log" to "economy_logs" and update "shops" with prefixes
boolean needsUpdate2 = false; // create field table and set database version
try (PreparedStatement ps = con.prepareStatement(queryGetTable)) {
ps.setString(1, "shop_log");
ResultSet rs = ps.executeQuery();
if (rs.next()) {
needsUpdate1 = true;
}
}
try (PreparedStatement ps = con.prepareStatement(queryGetTable)) {
ps.setString(1, tableFields);
ResultSet rs = ps.executeQuery();
if (!rs.next()) {
needsUpdate2 = true;
}
}
if (needsUpdate1) {
String queryRenameTableLogouts = "ALTER TABLE player_logout RENAME TO " + tableLogouts;
String queryRenameTableLogs = "ALTER TABLE shop_log RENAME TO backup_shop_log"; // for backup
String queryRenameTableShops = "ALTER TABLE shops RENAME TO backup_shops"; // for backup
plugin.getLogger().info("Updating database... (#1)");
// Rename logout table
try (Statement s = con.createStatement()) {
s.executeUpdate(queryRenameTableLogouts);
}
// Backup shops table
try (Statement s = con.createStatement()) {
s.executeUpdate(queryRenameTableShops);
}
// Backup log table
try (Statement s = con.createStatement()) {
s.executeUpdate(queryRenameTableLogs);
}
// Create new shops table
try (Statement s = con.createStatement()) {
s.executeUpdate(getQueryCreateTableShops());
}
// Create new log table
try (Statement s = con.createStatement()) {
s.executeUpdate(getQueryCreateTableLog());
}
// Convert shop table
try (Statement s = con.createStatement()) {
ResultSet rs = s.executeQuery("SELECT id,product FROM backup_shops");
while (rs.next()) {
ItemStack is = Utils.decode(rs.getString("product"));
int amount = is.getAmount();
is.setAmount(1);
String product = Utils.encode(is);
String insertQuery = "INSERT INTO " + tableShops + " SELECT id,vendor,?,?,world,x,y,z,buyprice,sellprice,shoptype FROM backup_shops WHERE id = ?";
try (PreparedStatement ps = con.prepareStatement(insertQuery)) {
ps.setString(1, product);
ps.setInt(2, amount);
ps.setInt(3, rs.getInt("id"));
ps.executeUpdate();
}
}
}
// Convert log table
try (Statement s = con.createStatement()) {
ResultSet rs = s.executeQuery("SELECT id,timestamp,executor,product,vendor FROM backup_shop_log");
while (rs.next()) {
String timestamp = rs.getString("timestamp");
long time = 0L;
try {
time = new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(timestamp).getTime();
} catch (ParseException e) {
plugin.debug("Failed to parse timestamp '" + timestamp + "': Time is set to 0");
plugin.debug(e);
}
String player = rs.getString("executor");
String playerUuid = player.substring(0, 36);
String playerName = player.substring(38, player.length() - 1);
String oldProduct = rs.getString("product");
String product = oldProduct.split(" x ")[1];
int amount = Integer.valueOf(oldProduct.split(" x ")[0]);
String vendor = rs.getString("vendor");
String vendorUuid = vendor.substring(0, 36);
String vendorName = vendor.substring(38).replaceAll("\\)( \\(ADMIN\\))?", "");
boolean admin = vendor.endsWith("(ADMIN)");
String insertQuery = "INSERT INTO " + tableLogs + " SELECT id,-1,timestamp,?,?,?,?,'Unknown',?,?,?,?,world,x,y,z,price,type FROM backup_shop_log WHERE id = ?";
try (PreparedStatement ps = con.prepareStatement(insertQuery)) {
ps.setLong(1, time);
ps.setString(2, playerName);
ps.setString(3, playerUuid);
ps.setString(4, product);
ps.setInt(5, amount);
ps.setString(6, vendorName);
ps.setString(7, vendorUuid);
ps.setBoolean(8, admin);
ps.setInt(9, rs.getInt("id"));
ps.executeUpdate();
}
}
}
}
if (needsUpdate2) {
plugin.getLogger().info("Updating database... (#2)");
// Create fields table
try (Statement s = con.createStatement()) {
s.executeUpdate(getQueryCreateTableFields());
}
}
// Set database version
try (PreparedStatement ps = con.prepareStatement(queryUpdateVersion)) {
ps.setInt(1, DATABASE_VERSION);
ps.executeUpdate();
}
}
}
/** /**
* (Re-)Connects to the the database and initializes it. <br> * <p>(Re-)Connects to the the database and initializes it.</p>
* Creates the table (if doesn't exist) and tests the connection *
* All tables are created if necessary and if the database
* structure has to be updated, that is done as well.
* *
* @param callback Callback that - if succeeded - returns the amount of shops * @param callback Callback that - if succeeded - returns the amount of shops
* that were found (as {@code int}) * that were found (as {@code int})
@ -69,6 +219,7 @@ public abstract class Database {
this.tableShops = Config.databaseTablePrefix + "shops"; this.tableShops = Config.databaseTablePrefix + "shops";
this.tableLogs = Config.databaseTablePrefix + "economy_logs"; this.tableLogs = Config.databaseTablePrefix + "economy_logs";
this.tableLogouts = Config.databaseTablePrefix + "player_logouts"; this.tableLogouts = Config.databaseTablePrefix + "player_logouts";
this.tableFields = Config.databaseTablePrefix + "fields";
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
@ -77,166 +228,28 @@ public abstract class Database {
dataSource = getDataSource(); dataSource = getDataSource();
String autoIncrement = Database.this instanceof SQLite ? "AUTOINCREMENT" : "AUTO_INCREMENT";
String queryCreateTableShopList =
"CREATE TABLE IF NOT EXISTS " + tableShops + " ("
+ "id INTEGER PRIMARY KEY " + autoIncrement + ","
+ "vendor TINYTEXT NOT NULL,"
+ "product TEXT NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "world TINYTEXT NOT NULL,"
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
+ "z INTEGER NOT NULL,"
+ "buyprice FLOAT NOT NULL,"
+ "sellprice FLOAT NOT NULL,"
+ "shoptype TINYTEXT NOT NULL)";
String queryCreateTableShopLog =
"CREATE TABLE IF NOT EXISTS " + tableLogs + " ("
+ "id INTEGER PRIMARY KEY " + autoIncrement + ","
+ "shop_id INTEGER NOT NULL,"
+ "timestamp TINYTEXT NOT NULL,"
+ "time LONG NOT NULL,"
+ "player_name TINYTEXT NOT NULL,"
+ "player_uuid TINYTEXT NOT NULL,"
+ "product_name TINYTEXT NOT NULL,"
+ "product TEXT NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "vendor_name TINYTEXT NOT NULL,"
+ "vendor_uuid TINYTEXT NOT NULL,"
+ "admin BIT NOT NULL,"
+ "world TINYTEXT NOT NULL,"
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
+ "z INTEGER NOT NULL,"
+ "price FLOAT NOT NULL,"
+ "type TINYTEXT NOT NULL)";
String queryCreateTablePlayerLogout =
"CREATE TABLE IF NOT EXISTS " + tableLogouts + " ("
+ "player VARCHAR(36) PRIMARY KEY NOT NULL,"
+ "time LONG NOT NULL)";
String queryCheckIfOldFormat =
Database.this instanceof SQLite ?
"SELECT name FROM sqlite_master WHERE type='table' AND name='shop_log'" :
"SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='shop_log'";
String queryRenameTableLogouts = "ALTER TABLE player_logout RENAME TO " + tableLogouts;
String queryRenameTableLogs = "ALTER TABLE shop_log RENAME TO backup_shop_log"; // for backup
String queryRenameTableShops = "ALTER TABLE shops RENAME TO backup_shops"; // for backup
try (Connection con = dataSource.getConnection()) { try (Connection con = dataSource.getConnection()) {
// Check if database is in old format // Update database structure if necessary
try (Statement s = con.createStatement()) { update();
ResultSet rs = s.executeQuery(queryCheckIfOldFormat);
if (rs.next()) {
plugin.getLogger().warning("Database is using old format and will be converted.");
try (Statement s2 = con.createStatement()) {
s.executeUpdate(queryRenameTableShops);
// Create new shops table
try (Statement s3 = con.createStatement()) {
s3.executeUpdate(queryCreateTableShopList);
}
// Create new log table
try (Statement s4 = con.createStatement()) {
s4.executeUpdate(queryCreateTableShopLog);
}
// Convert shop table
try (Statement s3 = con.createStatement()) {
ResultSet rs2 = s3.executeQuery("SELECT id,product FROM backup_shops");
while (rs2.next()) {
ItemStack is = Utils.decode(rs2.getString("product"));
int amount = is.getAmount();
is.setAmount(1);
String product = Utils.encode(is);
String insertQuery = "INSERT INTO " + tableShops + " SELECT id,vendor,?,?,world,x,y,z,buyprice,sellprice,shoptype FROM backup_shops WHERE id = ?";
try (PreparedStatement ps = con.prepareStatement(insertQuery)) {
ps.setString(1, product);
ps.setInt(2, amount);
ps.setInt(3, rs2.getInt("id"));
ps.executeUpdate();
}
}
}
// Convert log table
try (Statement s3 = con.createStatement()) {
ResultSet rs2 = s3.executeQuery("SELECT id,timestamp,executor,product,vendor FROM shop_log");
while (rs2.next()) {
String timestamp = rs2.getString("timestamp");
long time = 0L;
try {
time = new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(timestamp).getTime();
} catch (ParseException e) {
plugin.debug("Failed to parse timestamp '" + timestamp + "': Time is set to 0");
plugin.debug(e);
}
String player = rs2.getString("executor");
String playerUuid = player.substring(0, 36);
String playerName = player.substring(38, player.length() - 1);
String oldProduct = rs2.getString("product");
String product = oldProduct.split(" x ")[1];
int amount = Integer.valueOf(oldProduct.split(" x ")[0]);
String vendor = rs2.getString("vendor");
String vendorUuid = vendor.substring(0, 36);
String vendorName = vendor.substring(38).replaceAll("\\)( \\(ADMIN\\))?", "");
boolean admin = vendor.endsWith("(ADMIN)");
String insertQuery = "INSERT INTO " + tableLogs + " SELECT id,-1,timestamp,?,?,?,?,'Unknown',?,?,?,?,world,x,y,z,price,type FROM shop_log WHERE id = ?";
try (PreparedStatement ps = con.prepareStatement(insertQuery)) {
ps.setLong(1, time);
ps.setString(2, playerName);
ps.setString(3, playerUuid);
ps.setString(4, product);
ps.setInt(5, amount);
ps.setString(6, vendorName);
ps.setString(7, vendorUuid);
ps.setBoolean(8, admin);
ps.setInt(9, rs2.getInt("id"));
ps.executeUpdate();
}
}
}
// Rename log table
try (Statement s3 = con.createStatement()) {
s3.executeUpdate(queryRenameTableLogs);
}
// Rename logout table
try (Statement s3 = con.createStatement()) {
s3.executeUpdate(queryRenameTableLogouts);
}
}
}
}
// Create shop table // Create shop table
try (Statement s = con.createStatement()) { try (Statement s = con.createStatement()) {
s.executeUpdate(queryCreateTableShopList); s.executeUpdate(getQueryCreateTableShops());
} }
// Create log table // Create log table
try (Statement s = con.createStatement()) { try (Statement s = con.createStatement()) {
s.executeUpdate(queryCreateTableShopLog); s.executeUpdate(getQueryCreateTableLog());
} }
// Create logout table // Create logout table
try (Statement s = con.createStatement()) { try (Statement s = con.createStatement()) {
s.executeUpdate(queryCreateTablePlayerLogout); s.executeUpdate(getQueryCreateTableLogout());
}
// Create fields table
try (Statement s = con.createStatement()) {
s.executeUpdate(getQueryCreateTableFields());
} }
// Clean up economy log // Clean up economy log
@ -245,7 +258,7 @@ public abstract class Database {
} }
// Count shops entries in database // Count shops entries in database
try (Statement s = con.createStatement();) { try (Statement s = con.createStatement()) {
ResultSet rs = s.executeQuery("SELECT COUNT(id) FROM " + tableShops); ResultSet rs = s.executeQuery("SELECT COUNT(id) FROM " + tableShops);
if (rs.next()) { if (rs.next()) {
int count = rs.getInt(1); int count = rs.getInt(1);
@ -264,8 +277,8 @@ public abstract class Database {
callback.callSyncError(e); callback.callSyncError(e);
} }
plugin.getLogger().severe("Failed to initialize database"); plugin.getLogger().severe("Failed to initialize or connect to database");
plugin.debug("Failed to initialize database"); plugin.debug("Failed to initialize or connect to database");
plugin.debug(e); plugin.debug(e);
} }
} }

View File

@ -48,4 +48,62 @@ public class MySQL extends Database {
} }
}.runTaskAsynchronously(plugin); }.runTaskAsynchronously(plugin);
} }
@Override
String getQueryCreateTableShops() {
return "CREATE TABLE IF NOT EXISTS " + tableShops + " ("
+ "id INTEGER PRIMARY KEY AUTO_INCREMENT,"
+ "vendor TINYTEXT NOT NULL,"
+ "product TEXT NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "world TINYTEXT NOT NULL,"
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
+ "z INTEGER NOT NULL,"
+ "buyprice FLOAT NOT NULL,"
+ "sellprice FLOAT NOT NULL,"
+ "shoptype TINYTEXT NOT NULL)";
}
@Override
String getQueryCreateTableLog() {
return "CREATE TABLE IF NOT EXISTS " + tableLogs + " ("
+ "id INTEGER PRIMARY KEY AUTO_INCREMENT,"
+ "shop_id INTEGER NOT NULL,"
+ "timestamp TINYTEXT NOT NULL,"
+ "time LONG NOT NULL,"
+ "player_name TINYTEXT NOT NULL,"
+ "player_uuid TINYTEXT NOT NULL,"
+ "product_name TINYTEXT NOT NULL,"
+ "product TEXT NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "vendor_name TINYTEXT NOT NULL,"
+ "vendor_uuid TINYTEXT NOT NULL,"
+ "admin BIT NOT NULL,"
+ "world TINYTEXT NOT NULL,"
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
+ "z INTEGER NOT NULL,"
+ "price FLOAT NOT NULL,"
+ "type TINYTEXT NOT NULL)";
}
@Override
String getQueryCreateTableLogout() {
return "CREATE TABLE IF NOT EXISTS " + tableLogouts + " ("
+ "player VARCHAR(36) PRIMARY KEY NOT NULL,"
+ "time LONG NOT NULL)";
}
@Override
String getQueryCreateTableFields() {
return "CREATE TABLE IF NOT EXISTS " + tableFields + " ("
+ "field VARCHAR(32) PRIMARY KEY NOT NULL,"
+ "value INTEGER NOT NULL)";
}
@Override
String getQueryGetTable() {
return "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=?";
}
} }

View File

@ -68,4 +68,62 @@ public class SQLite extends Database {
runnable.run(); runnable.run();
} }
} }
@Override
String getQueryCreateTableShops() {
return "CREATE TABLE IF NOT EXISTS " + tableShops + " ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "vendor TINYTEXT NOT NULL,"
+ "product TEXT NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "world TINYTEXT NOT NULL,"
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
+ "z INTEGER NOT NULL,"
+ "buyprice FLOAT NOT NULL,"
+ "sellprice FLOAT NOT NULL,"
+ "shoptype TINYTEXT NOT NULL)";
}
@Override
String getQueryCreateTableLog() {
return "CREATE TABLE IF NOT EXISTS " + tableLogs + " ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "shop_id INTEGER NOT NULL,"
+ "timestamp TINYTEXT NOT NULL,"
+ "time LONG NOT NULL,"
+ "player_name TINYTEXT NOT NULL,"
+ "player_uuid TINYTEXT NOT NULL,"
+ "product_name TINYTEXT NOT NULL,"
+ "product TEXT NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "vendor_name TINYTEXT NOT NULL,"
+ "vendor_uuid TINYTEXT NOT NULL,"
+ "admin BIT NOT NULL,"
+ "world TINYTEXT NOT NULL,"
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
+ "z INTEGER NOT NULL,"
+ "price FLOAT NOT NULL,"
+ "type TINYTEXT NOT NULL)";
}
@Override
String getQueryCreateTableLogout() {
return "CREATE TABLE IF NOT EXISTS " + tableLogouts + " ("
+ "player VARCHAR(36) PRIMARY KEY NOT NULL,"
+ "time LONG NOT NULL)";
}
@Override
String getQueryCreateTableFields() {
return "CREATE TABLE IF NOT EXISTS " + tableFields + " ("
+ "field VARCHAR(32) PRIMARY KEY NOT NULL,"
+ "value INTEGER NOT NULL)";
}
@Override
String getQueryGetTable() {
return "SELECT name FROM sqlite_master WHERE type='table' AND name=?";
}
} }