mirror of
https://github.com/BentoBoxWorld/BentoBox.git
synced 2024-11-01 00:10:40 +01:00
Add ability for DataObjects to specify custom db table name (#1348)
Automated migration for existing databases is supported.
This commit is contained in:
parent
de124ab182
commit
feab01cde8
@ -97,9 +97,10 @@ public class Settings implements ConfigObject {
|
||||
@ConfigEntry(path = "general.database.use-ssl", since = "1.12.0")
|
||||
private boolean useSSL = false;
|
||||
|
||||
@ConfigComment("Database table prefix character. Adds a prefix to the database tables. Not used by flatfile databases.")
|
||||
@ConfigComment("Database table prefix. Adds a prefix to the database tables. Not used by flatfile databases.")
|
||||
@ConfigComment("Only the characters A-Z, a-z, 0-9 can be used. Invalid characters will become an underscore.")
|
||||
@ConfigComment("Set this to a unique value if you are running multiple BentoBox instances that share a database.")
|
||||
@ConfigComment("Be careful about length - databases usually have a limit of 63 characters for table lengths")
|
||||
@ConfigEntry(path = "general.database.prefix-character", since = "1.13.0")
|
||||
private String databasePrefix = "";
|
||||
|
||||
@ -238,16 +239,16 @@ public class Settings implements ConfigObject {
|
||||
private boolean autoOwnershipTransferIgnoreRanks = false;
|
||||
|
||||
// Island deletion related settings
|
||||
@ConfigComment("Toggles whether islands, when players are resetting them, should be kept in the world or deleted.")
|
||||
@ConfigComment("* If set to 'true', whenever a player resets his island, his previous island will become unowned and won't be deleted from the world.")
|
||||
@ConfigComment(" You can, however, still delete those unowned islands through purging.")
|
||||
@ConfigComment(" On bigger servers, this can lead to an increasing world size.")
|
||||
@ConfigComment(" Yet, this allows admins to retrieve a player's old island in case of an improper use of the reset command.")
|
||||
@ConfigComment(" Admins can indeed re-add the player to his old island by registering him to it.")
|
||||
@ConfigComment("* If set to 'false', whenever a player resets his island, his previous island will be deleted from the world.")
|
||||
@ConfigComment(" This is the default behaviour.")
|
||||
@ConfigEntry(path = "island.deletion.keep-previous-island-on-reset", since = "1.13.0")
|
||||
private boolean keepPreviousIslandOnReset = false;
|
||||
@ConfigComment("Toggles whether islands, when players are resetting them, should be kept in the world or deleted.")
|
||||
@ConfigComment("* If set to 'true', whenever a player resets his island, his previous island will become unowned and won't be deleted from the world.")
|
||||
@ConfigComment(" You can, however, still delete those unowned islands through purging.")
|
||||
@ConfigComment(" On bigger servers, this can lead to an increasing world size.")
|
||||
@ConfigComment(" Yet, this allows admins to retrieve a player's old island in case of an improper use of the reset command.")
|
||||
@ConfigComment(" Admins can indeed re-add the player to his old island by registering him to it.")
|
||||
@ConfigComment("* If set to 'false', whenever a player resets his island, his previous island will be deleted from the world.")
|
||||
@ConfigComment(" This is the default behaviour.")
|
||||
@ConfigEntry(path = "island.deletion.keep-previous-island-on-reset", since = "1.13.0")
|
||||
private boolean keepPreviousIslandOnReset = false;
|
||||
|
||||
/* WEB */
|
||||
@ConfigComment("Toggle whether BentoBox can connect to GitHub to get data about updates and addons.")
|
||||
@ -639,7 +640,7 @@ public class Settings implements ConfigObject {
|
||||
*/
|
||||
public String getDatabasePrefix() {
|
||||
if (databasePrefix == null) databasePrefix = "";
|
||||
return databasePrefix.isEmpty() ? "" : databasePrefix.replaceAll("[^a-zA-Z0-9]", "_").substring(0,1);
|
||||
return databasePrefix.isEmpty() ? "" : databasePrefix.replaceAll("[^a-zA-Z0-9]", "_");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -649,23 +650,23 @@ public class Settings implements ConfigObject {
|
||||
this.databasePrefix = databasePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether islands, when reset, should be kept or deleted.
|
||||
* @return {@code true} if islands, when reset, should be kept; {@code false} otherwise.
|
||||
* @since 1.13.0
|
||||
*/
|
||||
public boolean isKeepPreviousIslandOnReset() {
|
||||
return keepPreviousIslandOnReset;
|
||||
}
|
||||
/**
|
||||
* Returns whether islands, when reset, should be kept or deleted.
|
||||
* @return {@code true} if islands, when reset, should be kept; {@code false} otherwise.
|
||||
* @since 1.13.0
|
||||
*/
|
||||
public boolean isKeepPreviousIslandOnReset() {
|
||||
return keepPreviousIslandOnReset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether islands, when reset, should be kept or deleted.
|
||||
* @param keepPreviousIslandOnReset {@code true} if islands, when reset, should be kept; {@code false} otherwise.
|
||||
* @since 1.13.0
|
||||
*/
|
||||
public void setKeepPreviousIslandOnReset(boolean keepPreviousIslandOnReset) {
|
||||
this.keepPreviousIslandOnReset = keepPreviousIslandOnReset;
|
||||
}
|
||||
/**
|
||||
* Sets whether islands, when reset, should be kept or deleted.
|
||||
* @param keepPreviousIslandOnReset {@code true} if islands, when reset, should be kept; {@code false} otherwise.
|
||||
* @since 1.13.0
|
||||
*/
|
||||
public void setKeepPreviousIslandOnReset(boolean keepPreviousIslandOnReset) {
|
||||
this.keepPreviousIslandOnReset = keepPreviousIslandOnReset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a MongoDB client connection URI to override default connection options.
|
||||
|
@ -10,18 +10,21 @@ import org.bukkit.Bukkit;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.mongodb.MongoClientException;
|
||||
import com.mongodb.MongoNamespace;
|
||||
import com.mongodb.MongoTimeoutException;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.FindOneAndReplaceOptions;
|
||||
import com.mongodb.client.model.IndexOptions;
|
||||
import com.mongodb.client.model.Indexes;
|
||||
import com.mongodb.client.model.RenameCollectionOptions;
|
||||
import com.mongodb.util.JSON;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.database.DatabaseConnector;
|
||||
import world.bentobox.bentobox.database.json.AbstractJSONDatabaseHandler;
|
||||
import world.bentobox.bentobox.database.objects.DataObject;
|
||||
import world.bentobox.bentobox.database.objects.Table;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -59,7 +62,15 @@ public class MongoDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
||||
plugin.logError("Could not connect to the database. Are the credentials in the config.yml file correct?");
|
||||
connected = false;
|
||||
} else {
|
||||
collection = database.getCollection(plugin.getSettings().getDatabasePrefix() + dataObject.getCanonicalName());
|
||||
// Check for old collections
|
||||
String oldName = plugin.getSettings().getDatabasePrefix() + type.getCanonicalName();
|
||||
String newName = getName(plugin, dataObject);
|
||||
if (!oldName.equals((newName)) && collectionExists(database, oldName) && !collectionExists(database, newName)){
|
||||
collection = database.getCollection(oldName);
|
||||
collection.renameCollection(new MongoNamespace(database.getName(), newName));
|
||||
} else {
|
||||
collection = database.getCollection(newName);
|
||||
}
|
||||
IndexOptions indexOptions = new IndexOptions().unique(true);
|
||||
collection.createIndex(Indexes.text(UNIQUEID), indexOptions);
|
||||
}
|
||||
@ -80,6 +91,23 @@ public class MongoDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean collectionExists(MongoDatabase database, final String collectionName) {
|
||||
for (final String name : database.listCollectionNames()) {
|
||||
if (name.equalsIgnoreCase(collectionName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getName(BentoBox plugin, Class<T> type) {
|
||||
return plugin.getSettings().getDatabasePrefix() +
|
||||
(type.getAnnotation(Table.class) == null ?
|
||||
type.getCanonicalName()
|
||||
: type.getAnnotation(Table.class)
|
||||
.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> loadObjects() {
|
||||
List<T> list = new ArrayList<>();
|
||||
@ -147,7 +175,7 @@ public class MongoDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
||||
try {
|
||||
collection.findOneAndDelete(new Document(MONGO_ID, uniqueId));
|
||||
} catch (Exception e) {
|
||||
plugin.logError("Could not delete object " + plugin.getSettings().getDatabasePrefix() + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
|
||||
plugin.logError("Could not delete object " + getName(plugin, dataObject) + " " + uniqueId + " " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,7 @@ import world.bentobox.bentobox.util.Util;
|
||||
* @author tastybento
|
||||
* @author Poslovitch
|
||||
*/
|
||||
@Table(name = "Islands")
|
||||
public class Island implements DataObject {
|
||||
|
||||
// True if this island is deleted and pending deletion from the database
|
||||
|
@ -16,6 +16,7 @@ import world.bentobox.bentobox.BentoBox;
|
||||
* @author tastybento
|
||||
* @since 1.1
|
||||
*/
|
||||
@Table(name = "IslandDeletion")
|
||||
public class IslandDeletion implements DataObject {
|
||||
|
||||
@Expose
|
||||
|
@ -9,6 +9,7 @@ import com.google.gson.annotations.Expose;
|
||||
* @author tastybento
|
||||
*
|
||||
*/
|
||||
@Table(name = "Names")
|
||||
public class Names implements DataObject {
|
||||
|
||||
@Expose
|
||||
|
@ -24,6 +24,7 @@ import world.bentobox.bentobox.util.Util;
|
||||
*
|
||||
* @author tastybento
|
||||
*/
|
||||
@Table(name = "Players")
|
||||
public class Players implements DataObject {
|
||||
@Expose
|
||||
private Map<Location, Integer> homeLocations = new HashMap<>();
|
||||
|
@ -0,0 +1,22 @@
|
||||
package world.bentobox.bentobox.database.objects;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(TYPE)
|
||||
/**
|
||||
* Annotation to explicitly name tables
|
||||
* @author tastybento
|
||||
* @since 1.14.0
|
||||
*/
|
||||
public @interface Table {
|
||||
/**
|
||||
* @return name of the table to be used in the database
|
||||
*/
|
||||
String name();
|
||||
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
package world.bentobox.bentobox.database.sql;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.database.objects.Table;
|
||||
|
||||
/**
|
||||
* Contains the SQL strings for the database.
|
||||
* The default strings are for MySQL, so only the deltas need to be supplied.
|
||||
@ -13,47 +16,68 @@ public class SQLConfiguration {
|
||||
private String objectExistsSQL;
|
||||
private String schemaSQL;
|
||||
private String loadObjectsSQL;
|
||||
private String renameTableSQL;
|
||||
private final String tableName;
|
||||
private boolean renameRequired;
|
||||
private final String oldTableName;
|
||||
|
||||
/**
|
||||
* @param canonicalName - canonical name of the class being stored.
|
||||
*/
|
||||
public SQLConfiguration(String canonicalName) {
|
||||
schemaSQL = "CREATE TABLE IF NOT EXISTS `" + canonicalName +
|
||||
"` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) )";
|
||||
loadObjectsSQL = "SELECT `json` FROM `" + canonicalName + "`";
|
||||
loadObjectSQL = "SELECT `json` FROM `" + canonicalName + "` WHERE uniqueId = ? LIMIT 1";
|
||||
saveObjectSQL = "INSERT INTO `" + canonicalName + "` (json) VALUES (?) ON DUPLICATE KEY UPDATE json = ?";
|
||||
deleteObjectSQL = "DELETE FROM `" + canonicalName + "` WHERE uniqueId = ?";
|
||||
objectExistsSQL = "SELECT IF ( EXISTS( SELECT * FROM `" + canonicalName + "` WHERE `uniqueId` = ?), 1, 0)";
|
||||
public <T> SQLConfiguration(BentoBox plugin, Class<T> type) {
|
||||
// Set the table name
|
||||
oldTableName = plugin.getSettings().getDatabasePrefix() + type.getCanonicalName();
|
||||
this.tableName = plugin.getSettings().getDatabasePrefix() +
|
||||
(type.getAnnotation(Table.class) == null ?
|
||||
type.getCanonicalName()
|
||||
: type.getAnnotation(Table.class).name());
|
||||
// Only rename if there is a specific Table annotation
|
||||
renameRequired = !tableName.equals(oldTableName);
|
||||
schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) )");
|
||||
loadObjects("SELECT `json` FROM `[tableName]`");
|
||||
loadObject("SELECT `json` FROM `[tableName]` WHERE uniqueId = ? LIMIT 1");
|
||||
saveObject("INSERT INTO `[tableName]` (json) VALUES (?) ON DUPLICATE KEY UPDATE json = ?");
|
||||
deleteObject("DELETE FROM `[tableName]` WHERE uniqueId = ?");
|
||||
objectExists("SELECT IF ( EXISTS( SELECT * FROM `[tableName]` WHERE `uniqueId` = ?), 1, 0)");
|
||||
renameTable("SELECT Count(*) INTO @exists " +
|
||||
"FROM information_schema.tables " +
|
||||
"WHERE table_schema = '" + plugin.getSettings().getDatabaseName() + "' " +
|
||||
"AND table_type = 'BASE TABLE' " +
|
||||
"AND table_name = '[oldTableName]'; " +
|
||||
"SET @query = If(@exists=1,'RENAME TABLE `[oldTableName]` TO `[tableName]`','SELECT \\'nothing to rename\\' status'); " +
|
||||
"PREPARE stmt FROM @query;" +
|
||||
"EXECUTE stmt;");
|
||||
}
|
||||
|
||||
public SQLConfiguration loadObject(String string) {
|
||||
this.loadObjectSQL = string;
|
||||
this.loadObjectSQL = string.replaceFirst("\\[tableName\\]", tableName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SQLConfiguration saveObject(String string) {
|
||||
this.saveObjectSQL = string;
|
||||
this.saveObjectSQL = string.replaceFirst("\\[tableName\\]", tableName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SQLConfiguration deleteObject(String string) {
|
||||
this.deleteObjectSQL = string;
|
||||
this.deleteObjectSQL = string.replaceFirst("\\[tableName\\]", tableName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SQLConfiguration objectExists(String string) {
|
||||
this.objectExistsSQL = string;
|
||||
this.objectExistsSQL = string.replaceFirst("\\[tableName\\]", tableName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SQLConfiguration schema(String string) {
|
||||
this.schemaSQL = string;
|
||||
this.schemaSQL = string.replaceFirst("\\[tableName\\]", tableName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SQLConfiguration loadObjects(String string) {
|
||||
this.loadObjectsSQL = string;
|
||||
this.loadObjectsSQL = string.replaceFirst("\\[tableName\\]", tableName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SQLConfiguration renameTable(String string) {
|
||||
this.renameTableSQL = string.replaceAll("\\[tableName\\]", tableName).replaceAll("\\[oldTableName\\]", oldTableName);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -94,4 +118,29 @@ public class SQLConfiguration {
|
||||
return loadObjectsSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the renameTableSQL
|
||||
*/
|
||||
public String getRenameTableSQL() {
|
||||
return renameTableSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tableName
|
||||
*/
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the oldName
|
||||
*/
|
||||
public String getOldTableName() {
|
||||
return oldTableName;
|
||||
}
|
||||
|
||||
public boolean renameRequired() {
|
||||
return renameRequired;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -81,11 +81,19 @@ public class SQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
||||
* Creates the table in the database if it doesn't exist already
|
||||
*/
|
||||
protected void createSchema() {
|
||||
if (sqlConfig.renameRequired()) {
|
||||
// Transition from the old table name
|
||||
try (PreparedStatement pstmt = connection.prepareStatement(sqlConfig.getRenameTableSQL())) {
|
||||
pstmt.execute();
|
||||
} catch (SQLException e) {
|
||||
plugin.logError("Could not rename " + sqlConfig.getOldTableName() + " for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
|
||||
}
|
||||
}
|
||||
// Prepare and execute the database statements
|
||||
try (PreparedStatement pstmt = connection.prepareStatement(sqlConfig.getSchemaSQL())) {
|
||||
pstmt.executeUpdate();
|
||||
pstmt.execute();
|
||||
} catch (SQLException e) {
|
||||
plugin.logError("Problem trying to create schema for data object " + plugin.getSettings().getDatabasePrefix() + dataObject.getCanonicalName() + " " + e.getMessage());
|
||||
plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,8 +23,8 @@ public class MariaDBDatabaseHandler<T> extends SQLDatabaseHandler<T> {
|
||||
* @param databaseConnector - authentication details for the database
|
||||
*/
|
||||
MariaDBDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
|
||||
super(plugin, type, databaseConnector, new SQLConfiguration(plugin.getSettings().getDatabasePrefix() + type.getCanonicalName())
|
||||
.schema("CREATE TABLE IF NOT EXISTS `" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() +
|
||||
"` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))"));
|
||||
super(plugin, type, databaseConnector,
|
||||
new SQLConfiguration(plugin, type)
|
||||
.schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))"));
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,7 @@ public class MySQLDatabaseHandler<T> extends SQLDatabaseHandler<T> {
|
||||
* @param dbConnecter - authentication details for the database
|
||||
*/
|
||||
MySQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector dbConnecter) {
|
||||
super(plugin, type, dbConnecter, new SQLConfiguration(plugin.getSettings().getDatabasePrefix() + type.getCanonicalName())
|
||||
.schema("CREATE TABLE IF NOT EXISTS `" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() +
|
||||
"` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB"));
|
||||
super(plugin, type, dbConnecter, new SQLConfiguration(plugin, type)
|
||||
.schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB"));
|
||||
}
|
||||
}
|
||||
|
@ -30,21 +30,22 @@ public class PostgreSQLDatabaseHandler<T> extends SQLDatabaseHandler<T> {
|
||||
* @param databaseConnector Contains the settings to create a connection to the database
|
||||
*/
|
||||
PostgreSQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
|
||||
super(plugin, type, databaseConnector, new SQLConfiguration(plugin.getSettings().getDatabasePrefix() + type.getCanonicalName())
|
||||
super(plugin, type, databaseConnector, new SQLConfiguration(plugin, type)
|
||||
// Set uniqueid as the primary key (index). Postgresql convention is to use lower case field names
|
||||
// Postgresql also uses double quotes (") instead of (`) around tables names with dots.
|
||||
.schema("CREATE TABLE IF NOT EXISTS \"" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "\" (uniqueid VARCHAR PRIMARY KEY, json jsonb NOT NULL)")
|
||||
.loadObject("SELECT * FROM \"" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "\" WHERE uniqueid = ? LIMIT 1")
|
||||
.deleteObject("DELETE FROM \"" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "\" WHERE uniqueid = ?")
|
||||
.schema("CREATE TABLE IF NOT EXISTS \"[tableName]\" (uniqueid VARCHAR PRIMARY KEY, json jsonb NOT NULL)")
|
||||
.loadObject("SELECT * FROM \"[tableName]\" WHERE uniqueid = ? LIMIT 1")
|
||||
.deleteObject("DELETE FROM \"[tableName]\" WHERE uniqueid = ?")
|
||||
// uniqueId has to be added into the row explicitly so we need to override the saveObject method
|
||||
// The json value is a string but has to be cast to json when done in Java
|
||||
.saveObject("INSERT INTO \"" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "\" (uniqueid, json) VALUES (?, cast(? as json)) "
|
||||
.saveObject("INSERT INTO \"[tableName]\" (uniqueid, json) VALUES (?, cast(? as json)) "
|
||||
// This is the Postgresql version of UPSERT.
|
||||
+ "ON CONFLICT (uniqueid) "
|
||||
+ "DO UPDATE SET json = cast(? as json)")
|
||||
.loadObjects("SELECT json FROM \"" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "\"")
|
||||
.loadObjects("SELECT json FROM \"[tableName]\"")
|
||||
// Postgres exists function returns true or false natively
|
||||
.objectExists("SELECT EXISTS(SELECT * FROM \"" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "\" WHERE uniqueid = ?)")
|
||||
.objectExists("SELECT EXISTS(SELECT * FROM \"[tableName]\" WHERE uniqueid = ?)")
|
||||
.renameTable("ALTER TABLE IF EXISTS \"[oldTableName]\" RENAME TO \"[tableName]\"")
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -32,11 +32,42 @@ public class SQLiteDatabaseHandler<T> extends SQLDatabaseHandler<T> {
|
||||
* @param databaseConnector Contains the settings to create a connection to the database
|
||||
*/
|
||||
protected SQLiteDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
|
||||
super(plugin, type, databaseConnector, new SQLConfiguration(plugin.getSettings().getDatabasePrefix() + type.getCanonicalName())
|
||||
.schema("CREATE TABLE IF NOT EXISTS `" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "` (json JSON, uniqueId VARCHAR(255) NOT NULL PRIMARY KEY)")
|
||||
.saveObject("INSERT INTO `" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName()
|
||||
+ "` (json, uniqueId) VALUES (?, ?) ON CONFLICT(uniqueId) DO UPDATE SET json = ?")
|
||||
.objectExists("SELECT EXISTS (SELECT 1 FROM `" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "` WHERE `uniqueId` = ?)"));
|
||||
super(plugin, type, databaseConnector, new SQLConfiguration(plugin, type)
|
||||
.schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) NOT NULL PRIMARY KEY)")
|
||||
.saveObject("INSERT INTO `[tableName]` (json, uniqueId) VALUES (?, ?) ON CONFLICT(uniqueId) DO UPDATE SET json = ?")
|
||||
.objectExists("SELECT EXISTS (SELECT 1 FROM `[tableName]` WHERE `uniqueId` = ?)")
|
||||
.renameTable("ALTER TABLE `[oldTableName]` RENAME TO `[tableName]`"));
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Creates the table in the database if it doesn't exist already
|
||||
*/
|
||||
protected void createSchema() {
|
||||
if (getSqlConfig().renameRequired()) {
|
||||
// SQLite does not have a rename if exists command so we have to manually check if the old table exists
|
||||
String sql = "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name='" + getSqlConfig().getOldTableName() + "' COLLATE NOCASE)";
|
||||
try (PreparedStatement pstmt = getConnection().prepareStatement(sql)) {
|
||||
ResultSet resultSet = pstmt.executeQuery();
|
||||
if (resultSet.next() && resultSet.getBoolean(1)) {
|
||||
// Transition from the old table name
|
||||
try (PreparedStatement pstmt2 = getConnection().prepareStatement(getSqlConfig().getRenameTableSQL())) {
|
||||
pstmt2.execute();
|
||||
} catch (SQLException e) {
|
||||
plugin.logError("Could not rename " + getSqlConfig().getOldTableName() + " for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.logError("Could not check if " + getSqlConfig().getOldTableName() + " exists for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
// Prepare and execute the database statements
|
||||
try (PreparedStatement pstmt = getConnection().prepareStatement(getSqlConfig().getSchemaSQL())) {
|
||||
pstmt.execute();
|
||||
} catch (SQLException e) {
|
||||
plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -147,7 +147,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
when(resultSet.next()).thenReturn(true, true, true, false);
|
||||
when(ps.executeQuery(Mockito.anyString())).thenReturn(resultSet);
|
||||
List<Island> objects = handler.loadObjects();
|
||||
verify(ps).executeQuery("SELECT `json` FROM `world.bentobox.bentobox.database.objects.Island`");
|
||||
verify(ps).executeQuery("SELECT `json` FROM `Islands`");
|
||||
assertTrue(objects.size() == 3);
|
||||
assertEquals("xyz", objects.get(2).getUniqueId());
|
||||
}
|
||||
@ -166,7 +166,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
when(resultSet.next()).thenReturn(true, true, true, false);
|
||||
when(ps.executeQuery(Mockito.anyString())).thenReturn(resultSet);
|
||||
List<Island> objects = handler.loadObjects();
|
||||
verify(ps).executeQuery("SELECT `json` FROM `aworld.bentobox.bentobox.database.objects.Island`");
|
||||
verify(ps).executeQuery("SELECT `json` FROM `aIslands`");
|
||||
assertTrue(objects.size() == 3);
|
||||
assertEquals("xyz", objects.get(2).getUniqueId());
|
||||
}
|
||||
@ -183,7 +183,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
when(resultSet.next()).thenReturn(true, true, true, false);
|
||||
when(ps.executeQuery(Mockito.anyString())).thenReturn(resultSet);
|
||||
List<Island> objects = handler.loadObjects();
|
||||
verify(ps).executeQuery("SELECT `json` FROM `world.bentobox.bentobox.database.objects.Island`");
|
||||
verify(ps).executeQuery("SELECT `json` FROM `Islands`");
|
||||
assertTrue(objects.isEmpty());
|
||||
verify(plugin, Mockito.times(3)).logError("Could not load object java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $");
|
||||
}
|
||||
@ -200,7 +200,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
when(resultSet.next()).thenReturn(true, true, true, false);
|
||||
when(ps.executeQuery(Mockito.anyString())).thenReturn(resultSet);
|
||||
List<Island> objects = handler.loadObjects();
|
||||
verify(ps).executeQuery("SELECT `json` FROM `world.bentobox.bentobox.database.objects.Island`");
|
||||
verify(ps).executeQuery("SELECT `json` FROM `Islands`");
|
||||
assertTrue(objects.isEmpty());
|
||||
verify(plugin).logError("Could not load objects SQL error");
|
||||
|
||||
@ -327,7 +327,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
when(plugin.isEnabled()).thenReturn(false);
|
||||
when(ps.execute()).thenThrow(new SQLException("fail!"));
|
||||
handler.saveObject(instance);
|
||||
verify(plugin).logError(eq("Could not save object world.bentobox.bentobox.database.objects.Island fail!"));
|
||||
verify(plugin).logError(eq("Could not save object Islands fail!"));
|
||||
|
||||
}
|
||||
|
||||
@ -375,7 +375,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
when(ps.executeQuery()).thenReturn(resultSet);
|
||||
when(resultSet.next()).thenReturn(false);
|
||||
assertFalse(handler.objectExists("hello"));
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `world.bentobox.bentobox.database.objects.Island` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `Islands` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
verify(ps).executeQuery();
|
||||
verify(ps).setString(1, "\"hello\"");
|
||||
}
|
||||
@ -391,7 +391,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
when(resultSet.next()).thenReturn(true);
|
||||
when(resultSet.getBoolean(eq(1))).thenReturn(false);
|
||||
assertFalse(handler.objectExists("hello"));
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `world.bentobox.bentobox.database.objects.Island` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `Islands` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
verify(ps).executeQuery();
|
||||
verify(ps).setString(1, "\"hello\"");
|
||||
}
|
||||
@ -407,7 +407,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
when(resultSet.next()).thenReturn(true);
|
||||
when(resultSet.getBoolean(eq(1))).thenReturn(true);
|
||||
assertTrue(handler.objectExists("hello"));
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `world.bentobox.bentobox.database.objects.Island` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `Islands` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
verify(ps).executeQuery();
|
||||
verify(ps).setString(1, "\"hello\"");
|
||||
}
|
||||
@ -425,7 +425,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
when(resultSet.next()).thenReturn(true);
|
||||
when(resultSet.getBoolean(eq(1))).thenReturn(true);
|
||||
assertTrue(handler.objectExists("hello"));
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `aworld.bentobox.bentobox.database.objects.Island` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `aIslands` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
verify(ps).executeQuery();
|
||||
verify(ps).setString(1, "\"hello\"");
|
||||
}
|
||||
@ -468,7 +468,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
when(plugin.isEnabled()).thenReturn(false);
|
||||
when(ps.execute()).thenThrow(new SQLException("fail!"));
|
||||
handler.deleteID("abc123");
|
||||
verify(plugin).logError(eq("Could not delete object world.bentobox.bentobox.database.objects.Island abc123 fail!"));
|
||||
verify(plugin).logError(eq("Could not delete object Islands abc123 fail!"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -489,7 +489,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
@Test
|
||||
public void testMySQLDatabaseHandlerCreateSchema() throws SQLException {
|
||||
verify(dbConn).createConnection(any());
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `world.bentobox.bentobox.database.objects.Island` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `Islands` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -501,7 +501,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
public void testMySQLDatabaseHandlerCreateSchemaPrefix() throws SQLException {
|
||||
when(settings.getDatabasePrefix()).thenReturn("a");
|
||||
verify(dbConn).createConnection(any());
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `aworld.bentobox.bentobox.database.objects.Island` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `aIslands` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB");
|
||||
}
|
||||
/**
|
||||
* Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#MySQLDatabaseHandler(world.bentobox.bentobox.BentoBox, java.lang.Class, world.bentobox.bentobox.database.DatabaseConnector)}.
|
||||
@ -509,7 +509,7 @@ public class MySQLDatabaseHandlerTest {
|
||||
*/
|
||||
@Test
|
||||
public void testMySQLDatabaseHandlerSchemaFail() throws SQLException {
|
||||
when(ps.executeUpdate()).thenThrow(new SQLException("oh no!"));
|
||||
when(ps.execute()).thenThrow(new SQLException("oh no!"));
|
||||
handler = new MySQLDatabaseHandler<>(plugin, Island.class, dbConn);
|
||||
verify(plugin).logError("Problem trying to create schema for data object world.bentobox.bentobox.database.objects.Island oh no!");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user