Adds a single character prefix to database tables (#1278)

* Adds a single character prefix to database tables

https://github.com/BentoBoxWorld/BentoBox/issues/1277

* Fix tests

* Fix bug with substring
This commit is contained in:
tastybento 2020-04-18 14:16:36 -07:00 committed by GitHub
parent b94f9db0a9
commit 85d5a3a6ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 104 additions and 22 deletions

View File

@ -97,6 +97,12 @@ 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("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.")
@ConfigEntry(path = "general.database.prefix-character", since = "1.13.0")
private String databasePrefix = "";
@ConfigComment("Allow FTB Autonomous Activator to work (will allow a pseudo player [CoFH] to place and break blocks and hang items)")
@ConfigComment("Add other fake player names here if required")
@ConfigEntry(path = "general.fakeplayers", experimental = true)
@ -610,4 +616,19 @@ public class Settings implements ConfigObject {
public void setInviteConfirmation(boolean inviteConfirmation) {
this.inviteConfirmation = inviteConfirmation;
}
/**
* @return the databasePrefix
*/
public String getDatabasePrefix() {
if (databasePrefix == null) databasePrefix = "";
return databasePrefix.isEmpty() ? "" : databasePrefix.replaceAll("[^a-zA-Z0-9]", "_").substring(0,1);
}
/**
* @param databasePrefix the databasePrefix to set
*/
public void setDatabasePrefix(String databasePrefix) {
this.databasePrefix = databasePrefix;
}
}

View File

@ -2,6 +2,7 @@ package world.bentobox.bentobox.commands;
import java.util.List;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
@ -47,7 +48,7 @@ public class BentoBoxMigrateCommand extends ConfirmableCommand {
// Migrate addons data
user.sendMessage("commands.bentobox.migrate.addons");
getPlugin().getAddonsManager().getDataObjects().forEach(t -> {
user.sendMessage("commands.bentobox.migrate.class", TextVariables.DESCRIPTION, t.getCanonicalName());
user.sendMessage("commands.bentobox.migrate.class", TextVariables.DESCRIPTION, BentoBox.getInstance().getSettings().getDatabasePrefix() + t.getCanonicalName());
new Database<>(getPlugin(), t).loadObjects();
user.sendMessage(MIGRATED);
});

View File

@ -58,7 +58,7 @@ 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(dataObject.getCanonicalName());
collection = database.getCollection(plugin.getSettings().getDatabasePrefix() + dataObject.getCanonicalName());
IndexOptions indexOptions = new IndexOptions().unique(true);
collection.createIndex(Indexes.text(UNIQUEID), indexOptions);
}
@ -140,7 +140,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 " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
plugin.logError("Could not delete object " + plugin.getSettings().getDatabasePrefix() + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
}
}

View File

@ -4,6 +4,7 @@ import world.bentobox.bentobox.BentoBox;
/**
* Contains fields that must be in any data object
* DataObject's canonical name must be no more than 62 characters long otherwise it may not fit in a database table name
* @author tastybento
*
*/

View File

@ -84,7 +84,7 @@ public class SQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
try (PreparedStatement pstmt = connection.prepareStatement(sqlConfig.getSchemaSQL())) {
pstmt.executeUpdate();
} catch (SQLException e) {
plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
plugin.logError("Problem trying to create schema for data object " + plugin.getSettings().getDatabasePrefix() + dataObject.getCanonicalName() + " " + e.getMessage());
}
}
@ -184,7 +184,7 @@ public class SQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
preparedStatement.setString(1, "\"" + uniqueId + "\"");
preparedStatement.execute();
} catch (Exception e) {
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
plugin.logError("Could not delete object " + plugin.getSettings().getDatabasePrefix() + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
}
}

View File

@ -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(type.getCanonicalName())
.schema("CREATE TABLE IF NOT EXISTS `" + type.getCanonicalName() +
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))"));
}
}

View File

@ -23,8 +23,8 @@ 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(type.getCanonicalName())
.schema("CREATE TABLE IF NOT EXISTS `" + type.getCanonicalName() +
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"));
}
}

View File

@ -29,21 +29,21 @@ 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(type.getCanonicalName())
super(plugin, type, databaseConnector, new SQLConfiguration(plugin.getSettings().getDatabasePrefix() + type.getCanonicalName())
// 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 \"" + type.getCanonicalName() + "\" (uniqueid VARCHAR PRIMARY KEY, json jsonb NOT NULL)")
.loadObject("SELECT * FROM \"" + type.getCanonicalName() + "\" WHERE uniqueid = ? LIMIT 1")
.deleteObject("DELETE FROM \"" + type.getCanonicalName() + "\" WHERE uniqueid = ?")
.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 = ?")
// 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 \"" + type.getCanonicalName() + "\" (uniqueid, json) VALUES (?, cast(? as json)) "
.saveObject("INSERT INTO \"" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "\" (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 \"" + type.getCanonicalName() + "\"")
.loadObjects("SELECT json FROM \"" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "\"")
// Postgres exists function returns true or false natively
.objectExists("SELECT EXISTS(SELECT * FROM \"" + type.getCanonicalName() + "\" WHERE uniqueid = ?)")
.objectExists("SELECT EXISTS(SELECT * FROM \"" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "\" WHERE uniqueid = ?)")
);
}

View File

@ -31,11 +31,11 @@ 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(type.getCanonicalName())
.schema("CREATE TABLE IF NOT EXISTS `" + type.getCanonicalName() + "` (json JSON, uniqueId VARCHAR(255) NOT NULL PRIMARY KEY)")
.saveObject("INSERT INTO `" + type.getCanonicalName()
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 `" + type.getCanonicalName() + "` WHERE `uniqueId` = ?)"));
.objectExists("SELECT EXISTS (SELECT 1 FROM `" + plugin.getSettings().getDatabasePrefix() + type.getCanonicalName() + "` WHERE `uniqueId` = ?)"));
}
@Override
@ -71,7 +71,7 @@ public class SQLiteDatabaseHandler<T> extends SQLDatabaseHandler<T> {
preparedStatement.setString(1, uniqueId);
preparedStatement.executeUpdate();
} catch (Exception e) {
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
plugin.logError("Could not delete object " + plugin.getSettings().getDatabasePrefix() + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
}
});
}

View File

@ -61,6 +61,11 @@ general:
# Enable SSL connection to MongoDB, MariaDB, MySQL and PostgreSQL databases.
# Added since 1.12.0.
use-ssl: false
# Database table prefix character. Adds a prefix to the database tables. Not used by flatfile databases.
# Only the characters A-Z, a-z, 0-9 can be used. Invalid characters will become an underscore.
# Set this to a unique value if you are running multiple BentoBox instances that share a database.
# Added since 1.13.0
prefix-character: ''
# Allow FTB Autonomous Activator to work (will allow a pseudo player [CoFH] to place and break blocks and hang items)
# Add other fake player names here if required
# /!\ This feature is experimental and might not work as expected or might not work at all.

View File

@ -34,6 +34,7 @@ import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
@ -78,7 +79,8 @@ public class MySQLDatabaseHandlerTest {
private Connection connection;
@Mock
private PreparedStatement ps;
@Mock
private Settings settings;
/**
* @throws java.lang.Exception
*/
@ -88,6 +90,10 @@ public class MySQLDatabaseHandlerTest {
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
when(plugin.isEnabled()).thenReturn(true);
// Settings
when(plugin.getSettings()).thenReturn(settings);
when(settings.getDatabasePrefix()).thenReturn(""); // No prefix
// Bukkit
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getScheduler()).thenReturn(sch);
@ -146,6 +152,25 @@ public class MySQLDatabaseHandlerTest {
assertEquals("xyz", objects.get(2).getUniqueId());
}
/**
* Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#loadObjects()}.
* @throws SQLException
*/
@Test
@Ignore
public void testLoadObjectsPrefix() throws SQLException {
when(settings.getDatabasePrefix()).thenReturn("a");
ResultSet resultSet = mock(ResultSet.class);
when(resultSet.getString(any())).thenReturn(JSON);
// Three islands
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`");
assertTrue(objects.size() == 3);
assertEquals("xyz", objects.get(2).getUniqueId());
}
/**
* Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#loadObjects()}.
* @throws SQLException
@ -387,6 +412,24 @@ public class MySQLDatabaseHandlerTest {
verify(ps).setString(1, "\"hello\"");
}
/**
* Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#objectExists(java.lang.String)}.
* @throws SQLException
*/
@Test
@Ignore
public void testObjectExistsPrefix() throws SQLException {
when(settings.getDatabasePrefix()).thenReturn("a");
ResultSet resultSet = mock(ResultSet.class);
when(ps.executeQuery()).thenReturn(resultSet);
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(ps).executeQuery();
verify(ps).setString(1, "\"hello\"");
}
/**
* Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#objectExists(java.lang.String)}.
* @throws SQLException
@ -449,6 +492,17 @@ public class MySQLDatabaseHandlerTest {
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");
}
/**
* Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#MySQLDatabaseHandler(world.bentobox.bentobox.BentoBox, java.lang.Class, world.bentobox.bentobox.database.DatabaseConnector)}.
* @throws SQLException
*/
@Test
@Ignore
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");
}
/**
* Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#MySQLDatabaseHandler(world.bentobox.bentobox.BentoBox, java.lang.Class, world.bentobox.bentobox.database.DatabaseConnector)}.
* @throws SQLException