SQL Database abstraction (#831)

* Database abstraction WIP

* Removes code duplication in the databases

Fixes a regression bug on database connections - more than 1 were being
made again.

* Added ignores to tests because they run async now
This commit is contained in:
tastybento 2019-07-11 00:31:28 -07:00 committed by Florian CUNY
parent b5367200df
commit 1c1996ba4c
45 changed files with 554 additions and 1730 deletions

View File

@ -3,7 +3,11 @@ package world.bentobox.bentobox.database;
import java.beans.IntrospectionException; import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.List; import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -19,6 +23,21 @@ import world.bentobox.bentobox.api.addons.Addon;
*/ */
public abstract class AbstractDatabaseHandler<T> { public abstract class AbstractDatabaseHandler<T> {
/**
* FIFO queue for saves or deletions. Note that the assumption here is that most database objects will be held
* in memory because loading is not handled with this queue. That means that it is theoretically
* possible to load something before it has been saved. So, in general, load your objects and then
* save them async only when you do not need the data again immediately.
*/
protected Queue<Runnable> processQueue;
/**
* Async save task that runs repeatedly
*/
private BukkitTask asyncSaveTask;
protected boolean shutdown;
/** /**
* Name of the folder where databases using files will live * Name of the folder where databases using files will live
*/ */
@ -75,6 +94,33 @@ public abstract class AbstractDatabaseHandler<T> {
this.plugin = plugin; this.plugin = plugin;
this.databaseConnector = databaseConnector; this.databaseConnector = databaseConnector;
this.dataObject = type; this.dataObject = type;
// Run async queue
processQueue = new ConcurrentLinkedQueue<>();
if (plugin.isEnabled()) {
asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Loop continuously
while (!shutdown || !processQueue.isEmpty()) {
// This catches any databases that are not explicitly closed
if (!plugin.isEnabled()) {
shutdown = true;
}
while (!processQueue.isEmpty()) {
processQueue.poll().run();
}
// Clear the queue and then sleep
try {
Thread.sleep(25);
} catch (InterruptedException e) {
plugin.logError("Thread sleep error " + e.getMessage());
Thread.currentThread().interrupt();
}
}
// Cancel
asyncSaveTask.cancel();
databaseConnector.closeConnection(dataObject);
});
}
} }
protected AbstractDatabaseHandler() {} protected AbstractDatabaseHandler() {}

View File

@ -4,11 +4,11 @@ import java.util.Arrays;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.json.JSONDatabase; import world.bentobox.bentobox.database.json.JSONDatabase;
import world.bentobox.bentobox.database.mariadb.MariaDBDatabase;
import world.bentobox.bentobox.database.mongodb.MongoDBDatabase; import world.bentobox.bentobox.database.mongodb.MongoDBDatabase;
import world.bentobox.bentobox.database.mysql.MySQLDatabase; import world.bentobox.bentobox.database.sql.mariadb.MariaDBDatabase;
import world.bentobox.bentobox.database.postgresql.PostgreSQLDatabase; import world.bentobox.bentobox.database.sql.mysql.MySQLDatabase;
import world.bentobox.bentobox.database.sqlite.SQLiteDatabase; import world.bentobox.bentobox.database.sql.postgresql.PostgreSQLDatabase;
import world.bentobox.bentobox.database.sql.sqlite.SQLiteDatabase;
import world.bentobox.bentobox.database.transition.Json2MariaDBDatabase; import world.bentobox.bentobox.database.transition.Json2MariaDBDatabase;
import world.bentobox.bentobox.database.transition.Json2MongoDBDatabase; import world.bentobox.bentobox.database.transition.Json2MongoDBDatabase;
import world.bentobox.bentobox.database.transition.Json2MySQLDatabase; import world.bentobox.bentobox.database.transition.Json2MySQLDatabase;

View File

@ -41,6 +41,7 @@ public abstract class AbstractJSONDatabaseHandler<T> extends AbstractDatabaseHan
builder.disableHtmlEscaping(); builder.disableHtmlEscaping();
gson = builder.create(); gson = builder.create();
} }
protected Gson getGson() { protected Gson getGson() {

View File

@ -15,11 +15,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
@ -30,21 +26,6 @@ public class JSONDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
private static final String JSON = ".json"; private static final String JSON = ".json";
/**
* FIFO queue for saves or deletions. Note that the assumption here is that most database objects will be held
* in memory because loading is not handled with this queue. That means that it is theoretically
* possible to load something before it has been saved. So, in general, load your objects and then
* save them async only when you do not need the data again immediately.
*/
private Queue<Runnable> processQueue;
/**
* Async save task that runs repeatedly
*/
private BukkitTask asyncSaveTask;
private boolean shutdown;
/** /**
* Constructor * Constructor
* *
@ -55,30 +36,6 @@ public class JSONDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
*/ */
JSONDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) { JSONDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
super(plugin, type, databaseConnector); super(plugin, type, databaseConnector);
processQueue = new ConcurrentLinkedQueue<>();
if (plugin.isEnabled()) {
asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Loop continuously
while (!shutdown || !processQueue.isEmpty()) {
// This catches any databases that are not explicitly closed
if (!plugin.isEnabled()) {
shutdown = true;
}
while (!processQueue.isEmpty()) {
processQueue.poll().run();
}
// Clear the queue and then sleep
try {
Thread.sleep(25);
} catch (InterruptedException e) {
plugin.logError("Thread sleep error " + e.getMessage());
Thread.currentThread().interrupt();
}
}
// Cancel
asyncSaveTask.cancel();
});
}
} }
@Override @Override

View File

@ -1,81 +0,0 @@
package world.bentobox.bentobox.database.mariadb;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.DatabaseConnector;
/**
* @author barpec12
* @since 1.1
*/
public class MariaDBDatabaseConnector implements DatabaseConnector {
private String connectionUrl;
private DatabaseConnectionSettingsImpl dbSettings;
private Connection connection = null;
private Set<Class<?>> types = new HashSet<>();
/**
* Class for MariaDB database connections using the settings provided
* @param dbSettings - database settings
*/
MariaDBDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings) {
this.dbSettings = dbSettings;
connectionUrl = "jdbc:mysql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName()
+ "?autoReconnect=true&useSSL=false&allowMultiQueries=true";
}
@Override
public String getConnectionUrl() {
return connectionUrl;
}
@Override
@NonNull
public String getUniqueId(String tableName) {
// Not used
return "";
}
@Override
public boolean uniqueIdExists(String tableName, String key) {
// Not used
return false;
}
@Override
public void closeConnection(Class<?> type) {
types.remove(type);
if (types.isEmpty() && connection != null) {
try {
connection.close();
Bukkit.getLogger().info("Closed database connection");
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not close MariaDB database connection");
}
}
}
@Override
public Object createConnection(Class<?> type) {
types.add(type);
// Only get one connection at a time
if (connection == null) {
try {
connection = DriverManager.getConnection(connectionUrl, dbSettings.getUsername(), dbSettings.getPassword());
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not connect to the database! " + e.getMessage());
}
}
return connection;
}
}

View File

@ -1,283 +0,0 @@
package world.bentobox.bentobox.database.mariadb;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
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;
/**
*
* Class that inserts a <T> into the corresponding database-table.
*
* @author tastybento, barpec12
*
* @param <T>
*/
public class MariaDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
private static final String COULD_NOT_LOAD_OBJECTS = "Could not load objects ";
private static final String COULD_NOT_LOAD_OBJECT = "Could not load object ";
/**
* Connection to the database
*/
private Connection connection;
/**
* FIFO queue for saves or deletions. Note that the assumption here is that most database objects will be held
* in memory because loading is not handled with this queue. That means that it is theoretically
* possible to load something before it has been saved. So, in general, load your objects and then
* save them async only when you do not need the data again immediately.
*/
private Queue<Runnable> processQueue;
/**
* Async save task that runs repeatedly
*/
private BukkitTask asyncSaveTask;
private boolean shutdown;
/**
* Handles the connection to the database and creation of the initial database schema (tables) for
* the class that will be stored.
* @param plugin - plugin object
* @param type - the type of class to be stored in the database. Must inherit DataObject
* @param dbConnecter - authentication details for the database
*/
MariaDBDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector dbConnecter) {
super(plugin, type, dbConnecter);
connection = (Connection)dbConnecter.createConnection(dataObject);
if (connection == null) {
plugin.logError("Are the settings in config.yml correct?");
Bukkit.getPluginManager().disablePlugin(plugin);
return;
}
// Check if the table exists in the database and if not, create it
createSchema();
processQueue = new ConcurrentLinkedQueue<>();
if (plugin.isEnabled()) {
asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Loop continuously
while (!shutdown || !processQueue.isEmpty()) {
if (!plugin.isEnabled()) {
shutdown = true;
}
while (!processQueue.isEmpty()) {
processQueue.poll().run();
}
// Clear the queue and then sleep
try {
Thread.sleep(25);
} catch (InterruptedException e) {
plugin.logError("Thread sleep error " + e.getMessage());
Thread.currentThread().interrupt();
}
}
// Cancel
asyncSaveTask.cancel();
dbConnecter.closeConnection(dataObject);
});
}
}
/**
* Creates the table in the database if it doesn't exist already
*/
private void createSchema() {
String sql = "CREATE TABLE IF NOT EXISTS `" +
dataObject.getCanonicalName() +
"` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))";
// Prepare and execute the database statements
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.executeUpdate();
} catch (SQLException e) {
plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
}
}
@Override
public List<T> loadObjects() {
try (Statement preparedStatement = connection.createStatement()) {
return loadIt(preparedStatement);
} catch (SQLException e) {
plugin.logError(COULD_NOT_LOAD_OBJECTS + e.getMessage());
}
return Collections.emptyList();
}
private List<T> loadIt(Statement preparedStatement) {
List<T> list = new ArrayList<>();
String sb = "SELECT `json` FROM `" +
dataObject.getCanonicalName() +
"`";
try (ResultSet resultSet = preparedStatement.executeQuery(sb)) {
// Load all the results
Gson gson = getGson();
while (resultSet.next()) {
String json = resultSet.getString("json");
if (json != null) {
try {
T gsonResult = gson.fromJson(json, dataObject);
if (gsonResult != null) {
list.add(gsonResult);
}
} catch (JsonSyntaxException ex) {
plugin.logError(COULD_NOT_LOAD_OBJECT + ex.getMessage());
}
}
}
} catch (Exception e) {
plugin.logError(COULD_NOT_LOAD_OBJECTS + e.getMessage());
}
return list;
}
@Override
public T loadObject(String uniqueId) {
String sb = "SELECT `json` FROM `" + dataObject.getCanonicalName() + "` WHERE uniqueId = ? LIMIT 1";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
// If there is a result, we only want/need the first one
Gson gson = getGson();
return gson.fromJson(resultSet.getString("json"), dataObject);
}
} catch (Exception e) {
plugin.logError(COULD_NOT_LOAD_OBJECT + uniqueId + " " + e.getMessage());
}
} catch (SQLException e) {
plugin.logError(COULD_NOT_LOAD_OBJECT + uniqueId + " " + e.getMessage());
}
return null;
}
@Override
public void saveObject(T instance) {
// Null check
if (instance == null) {
plugin.logError("MySQL database request to store a null. ");
return;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
String sb = "INSERT INTO " +
"`" +
dataObject.getCanonicalName() +
"` (json) VALUES (?) ON DUPLICATE KEY UPDATE json = ?";
Gson gson = getGson();
String toStore = gson.toJson(instance);
if (plugin.isEnabled()) {
// Async
processQueue.add(() -> store(instance, toStore, sb));
} else {
// Sync
store(instance, toStore, sb);
}
}
private void store(T instance, String toStore, String sb) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
preparedStatement.setString(1, toStore);
preparedStatement.setString(2, toStore);
preparedStatement.execute();
} catch (SQLException e) {
plugin.logError("Could not save object " + instance.getClass().getName() + " " + e.getMessage());
}
}
/* (non-Javadoc)
* @see world.bentobox.bentobox.database.AbstractDatabaseHandler#deleteID(java.lang.String)
*/
@Override
public void deleteID(String uniqueId) {
if (plugin.isEnabled()) {
processQueue.add(() -> delete(uniqueId));
} else {
delete(uniqueId);
}
}
private void delete(String uniqueId) {
String sb = "DELETE FROM `" +
dataObject.getCanonicalName() +
"` WHERE uniqueId = ?";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
preparedStatement.execute();
} catch (Exception e) {
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
}
}
@Override
public void deleteObject(T instance) {
// Null check
if (instance == null) {
plugin.logError("MariaDB database request to delete a null.");
return;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
try {
Method getUniqueId = dataObject.getMethod("getUniqueId");
deleteID((String) getUniqueId.invoke(instance));
} catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}
}
@Override
public boolean objectExists(String uniqueId) {
// Create the query to see if this key exists
String query = "SELECT IF ( EXISTS( SELECT * FROM `" +
dataObject.getCanonicalName() +
"` WHERE `uniqueId` = ?), 1, 0)";
try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
return resultSet.getBoolean(1);
}
}
} catch (SQLException e) {
plugin.logError("Could not check if key exists in database! " + uniqueId + " " + e.getMessage());
}
return false;
}
@Override
public void close() {
shutdown = true;
}
}

View File

@ -1,4 +0,0 @@
/**
* Contains MySQL database managers.
*/
package world.bentobox.bentobox.database.mysql;

View File

@ -1,79 +0,0 @@
package world.bentobox.bentobox.database.postgresql;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.DatabaseConnector;
/**
* @since 1.6.0
* @author Poslovitch
*/
public class PostgreSQLDatabaseConnector implements DatabaseConnector {
private String connectionUrl;
private DatabaseConnectionSettingsImpl dbSettings;
private Connection connection = null;
private Set<Class<?>> types = new HashSet<>();
/**
* Class for PostgreSQL database connections using the settings provided
* @param dbSettings - database settings
*/
PostgreSQLDatabaseConnector(@NonNull DatabaseConnectionSettingsImpl dbSettings) {
this.dbSettings = dbSettings;
connectionUrl = "jdbc:postgresql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName()
+ "?autoReconnect=true&useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8";
}
@Override
public String getConnectionUrl() {
return connectionUrl;
}
@Override
public @NonNull String getUniqueId(String tableName) {
// Not used
return "";
}
@Override
public boolean uniqueIdExists(String tableName, String key) {
// Not used
return false;
}
@Override
public void closeConnection(Class<?> type) {
types.remove(type);
if (types.isEmpty() && connection != null) {
try {
connection.close();
Bukkit.getLogger().info("Closed database connection");
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not close PostgreSQL database connection");
}
}
}
@Override
public Object createConnection(Class<?> type) {
types.add(type);
// Only make one connection to the database
if (connection == null) {
try {
connection = DriverManager.getConnection(connectionUrl, dbSettings.getUsername(), dbSettings.getPassword());
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not connect to the database! " + e.getMessage());
}
}
return connection;
}
}

View File

@ -1,279 +0,0 @@
package world.bentobox.bentobox.database.postgresql;
import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
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;
/**
*
* @param <T>
*
* @since 1.6.0
* @author tastybento, Poslovitch
*/
public class PostgreSQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
private static final String COULD_NOT_LOAD_OBJECTS = "Could not load objects ";
private static final String COULD_NOT_LOAD_OBJECT = "Could not load object ";
/**
* Connection to the database
*/
private Connection connection;
/**
* FIFO queue for saves or deletions. Note that the assumption here is that most database objects will be held
* in memory because loading is not handled with this queue. That means that it is theoretically
* possible to load something before it has been saved. So, in general, load your objects and then
* save them async only when you do not need the data again immediately.
*/
private Queue<Runnable> processQueue;
/**
* Async save task that runs repeatedly
*/
private BukkitTask asyncSaveTask;
private boolean shutdown;
/**
* Constructor
*
* @param plugin
* @param type The type of the objects that should be created and filled with
* values from the database or inserted into the database
* @param databaseConnector Contains the settings to create a connection to the database
*/
protected PostgreSQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
super(plugin, type, databaseConnector);
connection = (Connection) databaseConnector.createConnection(dataObject);
if (connection == null) {
plugin.logError("Are the settings in config.yml correct?");
Bukkit.getPluginManager().disablePlugin(plugin);
return;
}
// Check if the table exists in the database and if not, create it
createSchema();
processQueue = new ConcurrentLinkedQueue<>();
if (plugin.isEnabled()) {
asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Loop continuously
while (!shutdown || !processQueue.isEmpty()) {
while (!processQueue.isEmpty()) {
processQueue.poll().run();
}
// Clear the queue and then sleep
try {
Thread.sleep(25);
} catch (InterruptedException e) {
plugin.logError("Thread sleep error " + e.getMessage());
Thread.currentThread().interrupt();
}
}
// Cancel
asyncSaveTask.cancel();
databaseConnector.closeConnection(dataObject);
});
}
}
/**
* Creates the table in the database if it doesn't exist already
*/
private void createSchema() {
String sql = "CREATE TABLE IF NOT EXISTS `" +
dataObject.getCanonicalName() +
"` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) )";
// Prepare and execute the database statements
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.executeUpdate();
} catch (SQLException e) {
plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
}
}
@Override
public List<T> loadObjects() throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, IntrospectionException, NoSuchMethodException {
try (Statement preparedStatement = connection.createStatement()) {
List<T> list = new ArrayList<>();
String sb = "SELECT `json` FROM `" +
dataObject.getCanonicalName() +
"`";
try (ResultSet resultSet = preparedStatement.executeQuery(sb)) {
// Load all the results
Gson gson = getGson();
while (resultSet.next()) {
String json = resultSet.getString("json");
if (json != null) {
try {
T gsonResult = gson.fromJson(json, dataObject);
if (gsonResult != null) {
list.add(gsonResult);
}
} catch (JsonSyntaxException ex) {
plugin.logError(COULD_NOT_LOAD_OBJECT + ex.getMessage());
plugin.logError(json);
}
}
}
} catch (Exception e) {
plugin.logError(COULD_NOT_LOAD_OBJECTS + e.getMessage());
}
return list;
} catch (SQLException e) {
plugin.logError(COULD_NOT_LOAD_OBJECTS + e.getMessage());
}
return Collections.emptyList();
}
@Nullable
@Override
public T loadObject(@NonNull String uniqueId) throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, IntrospectionException, NoSuchMethodException {
String sb = "SELECT `json` FROM `" + dataObject.getCanonicalName() + "` WHERE uniqueId = ? LIMIT 1";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
// If there is a result, we only want/need the first one
Gson gson = getGson();
return gson.fromJson(resultSet.getString("json"), dataObject);
}
} catch (Exception e) {
plugin.logError(COULD_NOT_LOAD_OBJECT + uniqueId + " " + e.getMessage());
}
} catch (SQLException e) {
plugin.logError(COULD_NOT_LOAD_OBJECT + uniqueId + " " + e.getMessage());
}
return null;
}
@Override
public void saveObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
// Null check
if (instance == null) {
plugin.logError("MySQL database request to store a null. ");
return;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
String sb = "INSERT INTO " +
"`" +
dataObject.getCanonicalName() +
"` (json) VALUES (?) ON DUPLICATE KEY UPDATE json = ?";
Gson gson = getGson();
String toStore = gson.toJson(instance);
if (plugin.isEnabled()) {
// Async
processQueue.add(() -> store(instance, toStore, sb));
} else {
// Sync
store(instance, toStore, sb);
}
}
private void store(T instance, String toStore, String sb) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
preparedStatement.setString(1, toStore);
preparedStatement.setString(2, toStore);
preparedStatement.execute();
} catch (SQLException e) {
plugin.logError("Could not save object " + instance.getClass().getName() + " " + e.getMessage());
}
}
@Override
public void deleteObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
// Null check
if (instance == null) {
plugin.logError("MySQL database request to delete a null.");
return;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
try {
Method getUniqueId = dataObject.getMethod("getUniqueId");
deleteID((String) getUniqueId.invoke(instance));
} catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}
}
private void delete(String uniqueId) {
String sb = "DELETE FROM `" +
dataObject.getCanonicalName() +
"` WHERE uniqueId = ?";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
preparedStatement.execute();
} catch (Exception e) {
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
}
}
@Override
public boolean objectExists(String uniqueId) {
// Create the query to see if this key exists
String query = "SELECT IF ( EXISTS( SELECT * FROM `" +
dataObject.getCanonicalName() +
"` WHERE `uniqueId` = ?), 1, 0)";
try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
return resultSet.getBoolean(1);
}
}
} catch (SQLException e) {
plugin.logError("Could not check if key exists in database! " + uniqueId + " " + e.getMessage());
}
return false;
}
@Override
public void close() {
shutdown = true;
}
@Override
public void deleteID(String uniqueId) {
if (plugin.isEnabled()) {
processQueue.add(() -> delete(uniqueId));
} else {
delete(uniqueId);
}
}
}

View File

@ -0,0 +1,97 @@
package world.bentobox.bentobox.database.sql;
/**
* Contains the SQL strings for the database.
* The default strings are for MySQL, so only the deltas need to be supplied.
* @author tastybento
*
*/
public class SQLConfiguration {
private String loadObjectSQL;
private String saveObjectSQL;
private String deleteObjectSQL;
private String objectExistsSQL;
private String schemaSQL;
private String loadObjectsSQL;
/**
* @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 SQLConfiguration loadObject(String string) {
this.loadObjectSQL = string;
return this;
}
public SQLConfiguration saveObject(String string) {
this.saveObjectSQL = string;
return this;
}
public SQLConfiguration deleteObject(String string) {
this.deleteObjectSQL = string;
return this;
}
public SQLConfiguration objectExists(String string) {
this.objectExistsSQL = string;
return this;
}
public SQLConfiguration schema(String string) {
this.schemaSQL = string;
return this;
}
public SQLConfiguration loadObjects(String string) {
this.loadObjectsSQL = string;
return this;
}
/**
* @return the loadObjectSQL
*/
public String getLoadObjectSQL() {
return loadObjectSQL;
}
/**
* @return the saveObjectSQL
*/
public String getSaveObjectSQL() {
return saveObjectSQL;
}
/**
* @return the deleteObjectSQL
*/
public String getDeleteObjectSQL() {
return deleteObjectSQL;
}
/**
* @return the objectExistsSQL
*/
public String getObjectExistsSQL() {
return objectExistsSQL;
}
/**
* @return the schemaSQL
*/
public String getSchemaSQL() {
return schemaSQL;
}
/**
* @return the loadItSQL
*/
public String getLoadObjectsSQL() {
return loadObjectsSQL;
}
}

View File

@ -1,4 +1,4 @@
package world.bentobox.bentobox.database.mysql; package world.bentobox.bentobox.database.sql;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
@ -12,21 +12,16 @@ import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl; import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.DatabaseConnector; import world.bentobox.bentobox.database.DatabaseConnector;
public class MySQLDatabaseConnector implements DatabaseConnector { public abstract class SQLDatabaseConnector implements DatabaseConnector {
private String connectionUrl; protected String connectionUrl;
private DatabaseConnectionSettingsImpl dbSettings; private DatabaseConnectionSettingsImpl dbSettings;
private Connection connection = null; protected static Connection connection = null;
private Set<Class<?>> types = new HashSet<>(); protected static Set<Class<?>> types = new HashSet<>();
/** public SQLDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings, String connectionUrl) {
* Class for MySQL database connections using the settings provided
* @param dbSettings - database settings
*/
MySQLDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings) {
this.dbSettings = dbSettings; this.dbSettings = dbSettings;
connectionUrl = "jdbc:mysql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName() this.connectionUrl = connectionUrl;
+ "?autoReconnect=true&useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8";
} }
@Override @Override
@ -55,7 +50,7 @@ public class MySQLDatabaseConnector implements DatabaseConnector {
connection.close(); connection.close();
Bukkit.getLogger().info("Closed database connection"); Bukkit.getLogger().info("Closed database connection");
} catch (SQLException e) { } catch (SQLException e) {
Bukkit.getLogger().severe("Could not close MySQL database connection"); Bukkit.getLogger().severe("Could not close database connection");
} }
} }
} }
@ -73,4 +68,5 @@ public class MySQLDatabaseConnector implements DatabaseConnector {
} }
return connection; return connection;
} }
} }

View File

@ -1,4 +1,4 @@
package world.bentobox.bentobox.database.mysql; package world.bentobox.bentobox.database.sql;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.sql.Connection; import java.sql.Connection;
@ -9,11 +9,8 @@ import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -26,13 +23,14 @@ import world.bentobox.bentobox.database.objects.DataObject;
/** /**
* *
* Abstract class that covers SQL style databases
* Class that inserts a <T> into the corresponding database-table. * Class that inserts a <T> into the corresponding database-table.
* *
* @author tastybento * @author tastybento
* *
* @param <T> * @param <T>
*/ */
public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> { public class SQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
private static final String COULD_NOT_LOAD_OBJECTS = "Could not load objects "; private static final String COULD_NOT_LOAD_OBJECTS = "Could not load objects ";
private static final String COULD_NOT_LOAD_OBJECT = "Could not load object "; private static final String COULD_NOT_LOAD_OBJECT = "Could not load object ";
@ -43,20 +41,9 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
private Connection connection; private Connection connection;
/** /**
* FIFO queue for saves or deletions. Note that the assumption here is that most database objects will be held * SQL configuration
* in memory because loading is not handled with this queue. That means that it is theoretically
* possible to load something before it has been saved. So, in general, load your objects and then
* save them async only when you do not need the data again immediately.
*/ */
private Queue<Runnable> processQueue; private SQLConfiguration sqlConfig;
/**
* Async save task that runs repeatedly
*/
private BukkitTask asyncSaveTask;
private boolean shutdown;
/** /**
* Handles the connection to the database and creation of the initial database schema (tables) for * Handles the connection to the database and creation of the initial database schema (tables) for
@ -64,53 +51,37 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
* @param plugin - plugin object * @param plugin - plugin object
* @param type - the type of class to be stored in the database. Must inherit DataObject * @param type - the type of class to be stored in the database. Must inherit DataObject
* @param dbConnecter - authentication details for the database * @param dbConnecter - authentication details for the database
* @param sqlConfiguration - SQL configuration
*/ */
MySQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector dbConnecter) { protected SQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector dbConnecter, SQLConfiguration sqlConfiguration) {
super(plugin, type, dbConnecter); super(plugin, type, dbConnecter);
connection = (Connection)dbConnecter.createConnection(dataObject); this.sqlConfig = sqlConfiguration;
if (connection == null) { if (setConnection((Connection)databaseConnector.createConnection(type))) {
plugin.logError("Are the settings in config.yml correct?"); // Check if the table exists in the database and if not, create it
Bukkit.getPluginManager().disablePlugin(plugin); createSchema();
return;
}
// Check if the table exists in the database and if not, create it
createSchema();
processQueue = new ConcurrentLinkedQueue<>();
if (plugin.isEnabled()) {
asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Loop continuously
while (!shutdown || !processQueue.isEmpty()) {
// This catches any databases that are not explicitly closed
if (!plugin.isEnabled()) {
shutdown = true;
}
while (!processQueue.isEmpty()) {
processQueue.poll().run();
}
// Clear the queue and then sleep
try {
Thread.sleep(25);
} catch (InterruptedException e) {
plugin.logError("Thread sleep error " + e.getMessage());
Thread.currentThread().interrupt();
}
}
// Cancel
asyncSaveTask.cancel();
dbConnecter.closeConnection(dataObject);
});
} }
} }
/**
* @return the sqlConfig
*/
public SQLConfiguration getSqlConfig() {
return sqlConfig;
}
/**
* @param sqlConfig the sqlConfig to set
*/
public void setSqlConfig(SQLConfiguration sqlConfig) {
this.sqlConfig = sqlConfig;
}
/** /**
* Creates the table in the database if it doesn't exist already * Creates the table in the database if it doesn't exist already
*/ */
private void createSchema() { protected void createSchema() {
String sql = "CREATE TABLE IF NOT EXISTS `" +
dataObject.getCanonicalName() +
"` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) )";
// Prepare and execute the database statements // Prepare and execute the database statements
try (PreparedStatement pstmt = connection.prepareStatement(sql)) { try (PreparedStatement pstmt = connection.prepareStatement(sqlConfig.getSchemaSQL())) {
pstmt.executeUpdate(); pstmt.executeUpdate();
} catch (SQLException e) { } 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 " + dataObject.getCanonicalName() + " " + e.getMessage());
@ -129,11 +100,7 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
private List<T> loadIt(Statement preparedStatement) { private List<T> loadIt(Statement preparedStatement) {
List<T> list = new ArrayList<>(); List<T> list = new ArrayList<>();
try (ResultSet resultSet = preparedStatement.executeQuery(sqlConfig.getLoadObjectsSQL())) {
String sb = "SELECT `json` FROM `" +
dataObject.getCanonicalName() +
"`";
try (ResultSet resultSet = preparedStatement.executeQuery(sb)) {
// Load all the results // Load all the results
Gson gson = getGson(); Gson gson = getGson();
while (resultSet.next()) { while (resultSet.next()) {
@ -158,8 +125,7 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
@Override @Override
public T loadObject(@NonNull String uniqueId) { public T loadObject(@NonNull String uniqueId) {
String sb = "SELECT `json` FROM `" + dataObject.getCanonicalName() + "` WHERE uniqueId = ? LIMIT 1"; try (PreparedStatement preparedStatement = connection.prepareStatement(sqlConfig.getLoadObjectSQL())) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId needs to be placed in quotes // UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\""); preparedStatement.setString(1, "\"" + uniqueId + "\"");
try (ResultSet resultSet = preparedStatement.executeQuery()) { try (ResultSet resultSet = preparedStatement.executeQuery()) {
@ -181,30 +147,19 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
public void saveObject(T instance) { public void saveObject(T instance) {
// Null check // Null check
if (instance == null) { if (instance == null) {
plugin.logError("MySQL database request to store a null. "); plugin.logError("SQL database request to store a null. ");
return; return;
} }
if (!(instance instanceof DataObject)) { if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName()); plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return; return;
} }
String sb = "INSERT INTO " + // Async
"`" + processQueue.add(() -> store(instance, sqlConfig.getSaveObjectSQL()));
dataObject.getCanonicalName() +
"` (json) VALUES (?) ON DUPLICATE KEY UPDATE json = ?";
Gson gson = getGson();
String toStore = gson.toJson(instance);
if (plugin.isEnabled()) {
// Async
processQueue.add(() -> store(instance, toStore, sb));
} else {
// Sync
store(instance, toStore, sb);
}
} }
private void store(T instance, String toStore, String sb) { private void store(T instance, String sb) {
String toStore = getGson().toJson(instance);
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) { try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
preparedStatement.setString(1, toStore); preparedStatement.setString(1, toStore);
preparedStatement.setString(2, toStore); preparedStatement.setString(2, toStore);
@ -219,18 +174,11 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
*/ */
@Override @Override
public void deleteID(String uniqueId) { public void deleteID(String uniqueId) {
if (plugin.isEnabled()) { processQueue.add(() -> delete(uniqueId));
processQueue.add(() -> delete(uniqueId));
} else {
delete(uniqueId);
}
} }
private void delete(String uniqueId) { private void delete(String uniqueId) {
String sb = "DELETE FROM `" + try (PreparedStatement preparedStatement = connection.prepareStatement(sqlConfig.getDeleteObjectSQL())) {
dataObject.getCanonicalName() +
"` WHERE uniqueId = ?";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId needs to be placed in quotes // UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\""); preparedStatement.setString(1, "\"" + uniqueId + "\"");
preparedStatement.execute(); preparedStatement.execute();
@ -243,7 +191,7 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
public void deleteObject(T instance) { public void deleteObject(T instance) {
// Null check // Null check
if (instance == null) { if (instance == null) {
plugin.logError("MySQL database request to delete a null."); plugin.logError("SQL database request to delete a null.");
return; return;
} }
if (!(instance instanceof DataObject)) { if (!(instance instanceof DataObject)) {
@ -260,12 +208,8 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
@Override @Override
public boolean objectExists(String uniqueId) { public boolean objectExists(String uniqueId) {
// Create the query to see if this key exists // Query to see if this key exists
String query = "SELECT IF ( EXISTS( SELECT * FROM `" + try (PreparedStatement preparedStatement = connection.prepareStatement(sqlConfig.getObjectExistsSQL())) {
dataObject.getCanonicalName() +
"` WHERE `uniqueId` = ?), 1, 0)";
try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
// UniqueId needs to be placed in quotes // UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\""); preparedStatement.setString(1, "\"" + uniqueId + "\"");
try (ResultSet resultSet = preparedStatement.executeQuery()) { try (ResultSet resultSet = preparedStatement.executeQuery()) {
@ -283,4 +227,25 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
public void close() { public void close() {
shutdown = true; shutdown = true;
} }
/**
* @return the connection
*/
public Connection getConnection() {
return connection;
}
/**
* @param connection the connection to set
* @return true if connection is not null
*/
public boolean setConnection(Connection connection) {
if (connection == null) {
plugin.logError("Are the settings in config.yml correct?");
Bukkit.getPluginManager().disablePlugin(plugin);
return false;
}
this.connection = connection;
return true;
}
} }

View File

@ -1,4 +1,4 @@
package world.bentobox.bentobox.database.mariadb; package world.bentobox.bentobox.database.sql.mariadb;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;

View File

@ -0,0 +1,21 @@
package world.bentobox.bentobox.database.sql.mariadb;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.sql.SQLDatabaseConnector;
/**
* @author barpec12
* @since 1.1
*/
public class MariaDBDatabaseConnector extends SQLDatabaseConnector {
/**
* Class for MariaDB database connections using the settings provided
* @param dbSettings - database settings
*/
MariaDBDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings) {
super(dbSettings, "jdbc:mysql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName()
+ "?autoReconnect=true&useSSL=false&allowMultiQueries=true");
}
}

View File

@ -0,0 +1,30 @@
package world.bentobox.bentobox.database.sql.mariadb;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.DatabaseConnector;
import world.bentobox.bentobox.database.sql.SQLDatabaseHandler;
import world.bentobox.bentobox.database.sql.SQLConfiguration;
/**
*
* Class that inserts a <T> into the corresponding database-table.
*
* @author tastybento, barpec12
*
* @param <T>
*/
public class MariaDBDatabaseHandler<T> extends SQLDatabaseHandler<T> {
/**
* Handles the connection to the database and creation of the initial database schema (tables) for
* the class that will be stored.
* @param plugin - plugin object
* @param type - the type of class to be stored in the database. Must inherit DataObject
* @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() +
"` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))"));
}
}

View File

@ -3,4 +3,4 @@
* @since 1.1 * @since 1.1
* @author barpec12 * @author barpec12
*/ */
package world.bentobox.bentobox.database.mariadb; package world.bentobox.bentobox.database.sql.mariadb;

View File

@ -1,4 +1,4 @@
package world.bentobox.bentobox.database.mysql; package world.bentobox.bentobox.database.sql.mysql;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;

View File

@ -0,0 +1,16 @@
package world.bentobox.bentobox.database.sql.mysql;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.sql.SQLDatabaseConnector;
public class MySQLDatabaseConnector extends SQLDatabaseConnector {
/**
* Class for MySQL database connections using the settings provided
* @param dbSettings - database settings
*/
MySQLDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings) {
super(dbSettings, "jdbc:mysql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName()
+ "?autoReconnect=true&useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8");
}
}

View File

@ -0,0 +1,28 @@
package world.bentobox.bentobox.database.sql.mysql;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.DatabaseConnector;
import world.bentobox.bentobox.database.sql.SQLDatabaseHandler;
import world.bentobox.bentobox.database.sql.SQLConfiguration;
/**
*
* Class that inserts a <T> into the corresponding database-table.
*
* @author tastybento
*
* @param <T>
*/
public class MySQLDatabaseHandler<T> extends SQLDatabaseHandler<T> {
/**
* Handles the connection to the database and creation of the initial database schema (tables) for
* the class that will be stored.
* @param plugin - plugin object
* @param type - the type of class to be stored in the database. Must inherit DataObject
* @param dbConnecter - authentication details for the database
*/
MySQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector dbConnecter) {
super(plugin, type, dbConnecter, new SQLConfiguration(type.getCanonicalName()));
}
}

View File

@ -0,0 +1,4 @@
/**
* Contains MySQL database managers.
*/
package world.bentobox.bentobox.database.sql.mysql;

View File

@ -1,4 +1,4 @@
package world.bentobox.bentobox.database.postgresql; package world.bentobox.bentobox.database.sql.postgresql;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;

View File

@ -0,0 +1,22 @@
package world.bentobox.bentobox.database.sql.postgresql;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.sql.SQLDatabaseConnector;
/**
* @since 1.6.0
* @author Poslovitch
*/
public class PostgreSQLDatabaseConnector extends SQLDatabaseConnector {
/**
* Class for PostgreSQL database connections using the settings provided
* @param dbSettings - database settings
*/
PostgreSQLDatabaseConnector(@NonNull DatabaseConnectionSettingsImpl dbSettings) {
super(dbSettings, "jdbc:postgresql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName()
+ "?autoReconnect=true&useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8");
}
}

View File

@ -0,0 +1,29 @@
package world.bentobox.bentobox.database.sql.postgresql;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.DatabaseConnector;
import world.bentobox.bentobox.database.sql.SQLDatabaseHandler;
import world.bentobox.bentobox.database.sql.SQLConfiguration;
/**
*
* @param <T>
*
* @since 1.6.0
* @author tastybento, Poslovitch
*/
public class PostgreSQLDatabaseHandler<T> extends SQLDatabaseHandler<T> {
/**
* Constructor
*
* @param plugin
* @param type The type of the objects that should be created and filled with
* values from the database or inserted into the database
* @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()));
}
}

View File

@ -2,4 +2,4 @@
* Contains PostgreSQL database managers. * Contains PostgreSQL database managers.
* @since 1.6.0 * @since 1.6.0
*/ */
package world.bentobox.bentobox.database.postgresql; package world.bentobox.bentobox.database.sql.postgresql;

View File

@ -1,4 +1,4 @@
package world.bentobox.bentobox.database.sqlite; package world.bentobox.bentobox.database.sql.sqlite;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;

View File

@ -0,0 +1,48 @@
package world.bentobox.bentobox.database.sql.sqlite;
import java.io.File;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.sql.SQLDatabaseConnector;
/**
* @since 1.6.0
* @author Poslovitch
*/
public class SQLiteDatabaseConnector extends SQLDatabaseConnector {
private static final String DATABASE_FOLDER_NAME = "database";
SQLiteDatabaseConnector(@NonNull BentoBox plugin) {
super(null, ""); // Not used by SQLite
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
if (!dataFolder.exists() && !dataFolder.mkdirs()) {
BentoBox.getInstance().logError("Could not create database folder!");
return;
}
connectionUrl = "jdbc:sqlite:" + dataFolder.getAbsolutePath() + File.separator + "database.db";
}
/* (non-Javadoc)
* @see world.bentobox.bentobox.database.sql.SQLDatabaseConnector#createConnection(java.lang.Class)
*/
@Override
public Object createConnection(Class<?> type) {
types.add(type);
// Only make one connection at a time
if (connection == null) {
try {
connection = DriverManager.getConnection(connectionUrl);
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not connect to the database! " + e.getMessage());
}
}
return connection;
}
}

View File

@ -0,0 +1,76 @@
package world.bentobox.bentobox.database.sql.sqlite;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.google.gson.Gson;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.DatabaseConnector;
import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.sql.SQLDatabaseHandler;
import world.bentobox.bentobox.database.sql.SQLConfiguration;
/**
* @since 1.6.0
* @author Poslovitch, tastybento
*/
public class SQLiteDatabaseHandler<T> extends SQLDatabaseHandler<T> {
/**
* Constructor
*
* @param plugin
* @param type The type of the objects that should be created and filled with
* values from the database or inserted into the database
* @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()
+ "` (json, uniqueId) VALUES (?, ?) ON CONFLICT(uniqueId) DO UPDATE SET json = ?")
.objectExists("SELECT EXISTS (SELECT 1 FROM `" + type.getCanonicalName() + "` WHERE `uniqueId` = ?)"));
}
@Override
public void saveObject(T instance) {
// Null check
if (instance == null) {
plugin.logError("MySQL database request to store a null. ");
return;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
processQueue.add(() -> {
Gson gson = getGson();
String toStore = gson.toJson(instance);
try (PreparedStatement preparedStatement = getConnection().prepareStatement(getSqlConfig().getSaveObjectSQL())) {
preparedStatement.setString(1, toStore);
preparedStatement.setString(2, ((DataObject)instance).getUniqueId());
preparedStatement.setString(3, toStore);
preparedStatement.execute();
} catch (SQLException e) {
plugin.logError("Could not save object " + instance.getClass().getName() + " " + e.getMessage());
}
});
}
@Override
public void deleteID(String uniqueId) {
processQueue.add(() -> {
try (PreparedStatement preparedStatement = getConnection().prepareStatement(getSqlConfig().getDeleteObjectSQL())) {
// UniqueId must *not* be placed in quotes
preparedStatement.setString(1, uniqueId);
int result = preparedStatement.executeUpdate();
if (result != 1) {
throw new SQLException("Delete did not affect any rows!");
}
} catch (Exception e) {
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
}
});
}
}

View File

@ -2,4 +2,4 @@
* Contains SQLite database managers. * Contains SQLite database managers.
* @since 1.6.0 * @since 1.6.0
*/ */
package world.bentobox.bentobox.database.sqlite; package world.bentobox.bentobox.database.sql.sqlite;

View File

@ -1,81 +0,0 @@
package world.bentobox.bentobox.database.sqlite;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.DatabaseConnector;
/**
* @since 1.6.0
* @author Poslovitch
*/
public class SQLiteDatabaseConnector implements DatabaseConnector {
private String connectionUrl;
private Connection connection = null;
private static final String DATABASE_FOLDER_NAME = "database";
private Set<Class<?>> types = new HashSet<>();
SQLiteDatabaseConnector(@NonNull BentoBox plugin) {
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
if (!dataFolder.exists()) {
if (!dataFolder.mkdirs()) {
BentoBox.getInstance().logError("Could not create database folder!");
}
}
connectionUrl = "jdbc:sqlite:" + dataFolder.getAbsolutePath() + File.separator + "database.db";
}
@Override
public String getConnectionUrl() {
return connectionUrl;
}
@Override
@NonNull
public String getUniqueId(String tableName) {
// Not used
return "";
}
@Override
public boolean uniqueIdExists(String tableName, String key) {
// Not used
return false;
}
@Override
public void closeConnection(Class<?> type) {
types.remove(type);
if (types.isEmpty() && connection != null) {
try {
connection.close();
Bukkit.getLogger().info("Closed database connection");
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not close SQLite database connection");
}
}
}
@Override
public Object createConnection(Class<?> type) {
types.add(type);
// Only make one connection at a time
if (connection == null) {
try {
connection = DriverManager.getConnection(connectionUrl);
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not connect to the database! " + e.getMessage());
}
}
return connection;
}
}

View File

@ -1,220 +0,0 @@
package world.bentobox.bentobox.database.sqlite;
import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
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;
/**
* @since 1.6.0
* @author Poslovitch
*/
public class SQLiteDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
/**
* Connection to the database
*/
private Connection connection;
/**
* Constructor
*
* @param plugin
* @param type The type of the objects that should be created and filled with
* values from the database or inserted into the database
* @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);
connection = (Connection) databaseConnector.createConnection(dataObject);
if (connection == null) {
plugin.logError("Are the settings in config.yml correct?");
Bukkit.getPluginManager().disablePlugin(plugin);
return;
}
// Check if the table exists in the database and if not, create it
createSchema();
}
/**
* Creates the table in the database if it doesn't exist already
*/
private void createSchema() {
String sql = "CREATE TABLE IF NOT EXISTS `" +
dataObject.getCanonicalName() +
"` (json JSON, uniqueId VARCHAR(255) NOT NULL PRIMARY KEY)";
// Prepare and execute the database statements
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.executeUpdate();
} catch (SQLException e) {
plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
}
}
@Override
public List<T> loadObjects() throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, IntrospectionException, NoSuchMethodException {
try (Statement preparedStatement = connection.createStatement()) {
List<T> list = new ArrayList<>();
String sb = "SELECT `json` FROM `" +
dataObject.getCanonicalName() +
"`";
try (ResultSet resultSet = preparedStatement.executeQuery(sb)) {
// Load all the results
Gson gson = getGson();
while (resultSet.next()) {
String json = resultSet.getString("json");
if (json != null) {
try {
T gsonResult = gson.fromJson(json, dataObject);
if (gsonResult != null) {
list.add(gsonResult);
}
} catch (JsonSyntaxException ex) {
plugin.logError("Could not load object " + ex.getMessage());
plugin.logError(json);
}
}
}
} catch (Exception e) {
plugin.logError("Could not load object " + e.getMessage());
}
return list;
} catch (SQLException e) {
plugin.logError("Could not load objects " + e.getMessage());
}
return Collections.emptyList();
}
@Nullable
@Override
public T loadObject(@NonNull String uniqueId) throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, IntrospectionException, NoSuchMethodException {
String sb = "SELECT `json` FROM `" + dataObject.getCanonicalName() + "` WHERE uniqueId = ? LIMIT 1";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
// If there is a result, we only want/need the first one
Gson gson = getGson();
return gson.fromJson(resultSet.getString("json"), dataObject);
}
} catch (Exception e) {
plugin.logError("Could not load object " + uniqueId + " " + e.getMessage());
}
} catch (SQLException e) {
plugin.logError("Could not load object " + uniqueId + " " + e.getMessage());
}
return null;
}
@Override
public void saveObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
// Null check
if (instance == null) {
plugin.logError("MySQL database request to store a null. ");
return;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
String sb = "INSERT INTO " +
"`" +
dataObject.getCanonicalName() +
"` (json, uniqueId) VALUES (?, ?) ON CONFLICT(uniqueId) DO UPDATE SET json = ?";
Gson gson = getGson();
String toStore = gson.toJson(instance);
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
preparedStatement.setString(1, toStore);
preparedStatement.setString(2, ((DataObject)instance).getUniqueId());
preparedStatement.setString(3, toStore);
preparedStatement.execute();
} catch (SQLException e) {
plugin.logError("Could not save object " + instance.getClass().getName() + " " + e.getMessage());
}
}
@Override
public void deleteObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
// Null check
if (instance == null) {
plugin.logError("SQLite database request to delete a null.");
return;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
try {
Method getUniqueId = dataObject.getMethod("getUniqueId");
deleteID((String) getUniqueId.invoke(instance));
} catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}
}
@Override
public boolean objectExists(String uniqueId) {
// Create the query to see if this key exists
String query = "SELECT EXISTS (SELECT 1 FROM `" +
dataObject.getCanonicalName() +
"` WHERE `uniqueId` = ?)";
try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
return resultSet.getBoolean(1);
}
}
} catch (SQLException e) {
plugin.logError("Could not check if key exists in database! " + uniqueId + " " + e.getMessage());
}
return false;
}
@Override
public void close() {
databaseConnector.closeConnection(dataObject);
}
@Override
public void deleteID(String uniqueId) {
String sb = "DELETE FROM `" +
dataObject.getCanonicalName() +
"` WHERE uniqueId = ?";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId must not be placed in quotes
preparedStatement.setString(1, uniqueId);
int result = preparedStatement.executeUpdate();
if (result != 1) {
throw new SQLException("Delete did not affect any rows!");
}
} catch (Exception e) {
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
}
}
}

View File

@ -3,7 +3,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.json.JSONDatabase; import world.bentobox.bentobox.database.json.JSONDatabase;
import world.bentobox.bentobox.database.mariadb.MariaDBDatabase; import world.bentobox.bentobox.database.sql.mariadb.MariaDBDatabase;
/** /**
* @author tastybento * @author tastybento

View File

@ -3,7 +3,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.json.JSONDatabase; import world.bentobox.bentobox.database.json.JSONDatabase;
import world.bentobox.bentobox.database.mysql.MySQLDatabase; import world.bentobox.bentobox.database.sql.mysql.MySQLDatabase;
/** /**
* @author tastybento * @author tastybento

View File

@ -3,7 +3,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.json.JSONDatabase; import world.bentobox.bentobox.database.json.JSONDatabase;
import world.bentobox.bentobox.database.postgresql.PostgreSQLDatabase; import world.bentobox.bentobox.database.sql.postgresql.PostgreSQLDatabase;
/** /**
* @author Poslovitch * @author Poslovitch

View File

@ -3,7 +3,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.json.JSONDatabase; import world.bentobox.bentobox.database.json.JSONDatabase;
import world.bentobox.bentobox.database.sqlite.SQLiteDatabase; import world.bentobox.bentobox.database.sql.sqlite.SQLiteDatabase;
/** /**

View File

@ -3,7 +3,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.json.JSONDatabase; import world.bentobox.bentobox.database.json.JSONDatabase;
import world.bentobox.bentobox.database.mariadb.MariaDBDatabase; import world.bentobox.bentobox.database.sql.mariadb.MariaDBDatabase;
/** /**

View File

@ -3,7 +3,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.json.JSONDatabase; import world.bentobox.bentobox.database.json.JSONDatabase;
import world.bentobox.bentobox.database.mysql.MySQLDatabase; import world.bentobox.bentobox.database.sql.mysql.MySQLDatabase;
/** /**
* @author tastybento * @author tastybento

View File

@ -3,7 +3,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.json.JSONDatabase; import world.bentobox.bentobox.database.json.JSONDatabase;
import world.bentobox.bentobox.database.postgresql.PostgreSQLDatabase; import world.bentobox.bentobox.database.sql.postgresql.PostgreSQLDatabase;
/** /**
* @author Poslovitch * @author Poslovitch

View File

@ -3,7 +3,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.json.JSONDatabase; import world.bentobox.bentobox.database.json.JSONDatabase;
import world.bentobox.bentobox.database.sqlite.SQLiteDatabase; import world.bentobox.bentobox.database.sql.sqlite.SQLiteDatabase;
/** /**

View File

@ -2,7 +2,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.mariadb.MariaDBDatabase; import world.bentobox.bentobox.database.sql.mariadb.MariaDBDatabase;
import world.bentobox.bentobox.database.yaml.YamlDatabase; import world.bentobox.bentobox.database.yaml.YamlDatabase;
/** /**

View File

@ -2,7 +2,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.mysql.MySQLDatabase; import world.bentobox.bentobox.database.sql.mysql.MySQLDatabase;
import world.bentobox.bentobox.database.yaml.YamlDatabase; import world.bentobox.bentobox.database.yaml.YamlDatabase;
/** /**

View File

@ -2,7 +2,7 @@ package world.bentobox.bentobox.database.transition;
import world.bentobox.bentobox.database.AbstractDatabaseHandler; import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.sqlite.SQLiteDatabase; import world.bentobox.bentobox.database.sql.sqlite.SQLiteDatabase;
import world.bentobox.bentobox.database.yaml.YamlDatabase; import world.bentobox.bentobox.database.yaml.YamlDatabase;

View File

@ -20,17 +20,14 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.configuration.MemorySection; import org.bukkit.configuration.MemorySection;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.scheduler.BukkitTask;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -59,26 +56,11 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
private static final String YML = ".yml"; private static final String YML = ".yml";
/**
* FIFO queue for saves. Note that the assumption here is that most database objects will be held
* in memory because loading is not handled with this queue. That means that it is theoretically
* possible to load something before it has been saved. So, in general, load your objects and then
* save them async only when you do not need the data again immediately.
*/
private Queue<Runnable> processQueue;
/**
* Async save task that runs repeatedly
*/
private BukkitTask asyncSaveTask;
/** /**
* Flag to indicate if this is a config or a pure object database (difference is in comments and annotations) * Flag to indicate if this is a config or a pure object database (difference is in comments and annotations)
*/ */
protected boolean configFlag; protected boolean configFlag;
/** /**
* Constructor * Constructor
* @param plugin - plugin * @param plugin - plugin
@ -87,26 +69,6 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
*/ */
YamlDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) { YamlDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
super(plugin, type, databaseConnector); super(plugin, type, databaseConnector);
processQueue = new ConcurrentLinkedQueue<>();
if (plugin.isEnabled()) {
asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Loop continuously
while (plugin.isEnabled() || !processQueue.isEmpty()) {
while (!processQueue.isEmpty()) {
processQueue.poll().run();
}
// Clear the queue and then sleep
try {
Thread.sleep(25);
} catch (InterruptedException e) {
plugin.logError("Thread sleep error " + e.getMessage());
Thread.currentThread().interrupt();
}
}
// Cancel
asyncSaveTask.cancel();
});
}
} }
/* (non-Javadoc) /* (non-Javadoc)

View File

@ -1,456 +0,0 @@
package world.bentobox.bentobox.database.mariadb;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
/**
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest( { Bukkit.class, BentoBox.class, Util.class })
public class MariaDBDatabaseHandlerTest {
private static final String JSON = "{\n" +
" \"deleted\": false,\n" +
" \"uniqueId\": \"xyz\",\n" +
" \"range\": 0,\n" +
" \"protectionRange\": 0,\n" +
" \"maxEverProtectionRange\": 0,\n" +
" \"createdDate\": 0,\n" +
" \"updatedDate\": 0,\n" +
" \"members\": {},\n" +
" \"spawn\": false,\n" +
" \"purgeProtected\": false,\n" +
" \"flags\": {},\n" +
" \"history\": [],\n" +
" \"levelHandicap\": 0,\n" +
" \"spawnPoint\": {},\n" +
" \"doNotLoad\": false,\n" +
" \"cooldowns\": {}\n" +
"}";
private MariaDBDatabaseHandler<Island> handler;
private Island instance;
private String UNIQUE_ID = "xyz";
@Mock
private MySQLDatabaseConnector dbConn;
@Mock
private BentoBox plugin;
@Mock
private BukkitScheduler sch;
@Mock
private PluginManager pluginManager;
@Mock
private Connection connection;
@Mock
private PreparedStatement ps;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
// Setup plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
when(plugin.isEnabled()).thenReturn(true);
// Bukkit
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getScheduler()).thenReturn(sch);
// Plugin Manager
pluginManager = mock(PluginManager.class);
when(Bukkit.getPluginManager()).thenReturn(pluginManager);
// MySQLDatabaseConnector
when(dbConn.createConnection(any())).thenReturn(connection);
// Queries
when(connection.prepareStatement(Mockito.anyString())).thenReturn(ps);
when(connection.createStatement()).thenReturn(ps);
ResultSet rs = mock(ResultSet.class);
when(ps.executeQuery()).thenReturn(rs);
when(ps.executeQuery(Mockito.anyString())).thenReturn(rs);
// Instance to save
instance = new Island();
instance.setUniqueId(UNIQUE_ID);
handler = new MariaDBDatabaseHandler<>(plugin, Island.class, dbConn);
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#loadObjects()}.
* @throws SQLException
*/
@Test
public void testLoadObjectsNoConnection() throws SQLException {
when(connection.createStatement()).thenThrow(new SQLException("no connection"));
handler.loadObjects();
verify(plugin).logError("Could not load objects no connection");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#loadObjects()}.
* @throws SQLException
*/
@Test
public void testLoadObjects() throws SQLException {
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 `world.bentobox.bentobox.database.objects.Island`");
assertTrue(objects.size() == 3);
assertEquals("xyz", objects.get(2).getUniqueId());
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#loadObjects()}.
* @throws SQLException
*/
@Test
public void testLoadObjectsBadJSON() throws SQLException {
ResultSet resultSet = mock(ResultSet.class);
when(resultSet.getString(any())).thenReturn("sfdasfasdfsfd");
// 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 `world.bentobox.bentobox.database.objects.Island`");
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 $");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#loadObjects()}.
* @throws SQLException
*/
@Test
public void testLoadObjectsError() throws SQLException {
ResultSet resultSet = mock(ResultSet.class);
when(resultSet.getString(any())).thenThrow(new SQLException("SQL error"));
// 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 `world.bentobox.bentobox.database.objects.Island`");
assertTrue(objects.isEmpty());
verify(plugin).logError("Could not load objects SQL error");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#loadObject(java.lang.String)}.
*/
@Test
public void testLoadObjectNoConnection() throws SQLException {
when(connection.prepareStatement(Mockito.anyString())).thenThrow(new SQLException("no connection"));
handler.loadObject("abc");
verify(plugin).logError("Could not load object abc no connection");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#loadObject(java.lang.String)}.
* @throws SQLException
*/
@Test
public void testLoadObject() throws SQLException {
ResultSet resultSet = mock(ResultSet.class);
when(resultSet.getString(any())).thenReturn(JSON);
when(resultSet.next()).thenReturn(true);
when(ps.executeQuery()).thenReturn(resultSet);
Island object = handler.loadObject("abc");
verify(ps).executeQuery();
verify(ps).setString(1, "\"abc\"");
verify(resultSet).next();
assertNotNull(object);
assertEquals("xyz", object.getUniqueId());
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#loadObject(java.lang.String)}.
* @throws SQLException
*/
@Test
public void testLoadObjectBadJSON() throws SQLException {
ResultSet resultSet = mock(ResultSet.class);
when(resultSet.getString(any())).thenReturn("afdsaf");
when(resultSet.next()).thenReturn(true);
when(ps.executeQuery()).thenReturn(resultSet);
Island object = handler.loadObject("abc");
assertNull(object);
verify(plugin).logError("Could not load object abc java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#loadObject(java.lang.String)}.
* @throws SQLException
*/
@Test
public void testLoadObjectError() throws SQLException {
ResultSet resultSet = mock(ResultSet.class);
when(resultSet.getString(any())).thenReturn(JSON);
when(resultSet.next()).thenThrow(new SQLException("SQL Exception"));
when(ps.executeQuery()).thenReturn(resultSet);
Island object = handler.loadObject("abc");
assertNull(object);
verify(plugin).logError("Could not load object abc SQL Exception");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#saveObject(java.lang.Object)}.
*/
@Test
public void testSaveObjectNull() {
handler.saveObject(null);
verify(plugin).logError(eq("MySQL database request to store a null. "));
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#saveObject(java.lang.Object)}.
*/
@Test
public void testSaveObjectNotDataObject() {
@SuppressWarnings("rawtypes")
MariaDBDatabaseHandler<List> h = new MariaDBDatabaseHandler<List>(plugin, List.class, dbConn);
h.saveObject(Collections.singletonList("test"));
verify(plugin).logError(eq("This class is not a DataObject: java.util.Collections$SingletonList"));
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#saveObject(java.lang.Object)}.
* @throws SQLException
*/
@Test
public void testSaveObject() throws SQLException {
// Disable plugin
when(plugin.isEnabled()).thenReturn(false);
handler.saveObject(instance);
verify(ps).execute();
verify(ps).setString(1, JSON);
verify(ps).setString(2, "{\n" +
" \"deleted\": false,\n" +
" \"uniqueId\": \"xyz\",\n" +
" \"range\": 0,\n" +
" \"protectionRange\": 0,\n" +
" \"maxEverProtectionRange\": 0,\n" +
" \"createdDate\": 0,\n" +
" \"updatedDate\": 0,\n" +
" \"members\": {},\n" +
" \"spawn\": false,\n" +
" \"purgeProtected\": false,\n" +
" \"flags\": {},\n" +
" \"history\": [],\n" +
" \"levelHandicap\": 0,\n" +
" \"spawnPoint\": {},\n" +
" \"doNotLoad\": false,\n" +
" \"cooldowns\": {}\n" +
"}");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#saveObject(java.lang.Object)}.
* @throws SQLException
*/
@Test
public void testSaveObjectFail() throws SQLException {
// Disable plugin
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!"));
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#deleteObject(java.lang.Object)}.
*/
@Test
public void testDeleteObjectNull() {
handler.deleteObject(null);
verify(plugin).logError(eq("MariaDB database request to delete a null."));
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#deleteObject(java.lang.Object)}.
*/
@Test
public void testDeleteObjectIncorrectType() {
@SuppressWarnings("rawtypes")
MariaDBDatabaseHandler<List> h = new MariaDBDatabaseHandler<List>(plugin, List.class, dbConn);
h.deleteObject(Collections.singletonList("test"));
verify(plugin).logError(eq("This class is not a DataObject: java.util.Collections$SingletonList"));
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#deleteObject(java.lang.Object)}.
* @throws SQLException
*/
@Test
public void testDeleteObject() throws SQLException {
// Disable plugin
when(plugin.isEnabled()).thenReturn(false);
handler.deleteObject(instance);
verify(ps).execute();
verify(ps).setString(1, "\"xyz\"");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#objectExists(java.lang.String)}.
* @throws SQLException
*/
@Test
public void testObjectExistsNot() throws SQLException {
ResultSet resultSet = mock(ResultSet.class);
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) )");
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `world.bentobox.bentobox.database.objects.Island` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))");
verify(ps).executeQuery();
verify(ps).setString(1, "\"hello\"");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#objectExists(java.lang.String)}.
* @throws SQLException
*/
@Test
public void testObjectExistsFalse() throws SQLException {
ResultSet resultSet = mock(ResultSet.class);
when(ps.executeQuery()).thenReturn(resultSet);
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) )");
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `world.bentobox.bentobox.database.objects.Island` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))");
verify(ps).executeQuery();
verify(ps).setString(1, "\"hello\"");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#objectExists(java.lang.String)}.
* @throws SQLException
*/
@Test
public void testObjectExists() throws SQLException {
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 `world.bentobox.bentobox.database.objects.Island` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) )");
verify(connection).prepareStatement("CREATE TABLE IF NOT EXISTS `world.bentobox.bentobox.database.objects.Island` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))");
verify(ps).executeQuery();
verify(ps).setString(1, "\"hello\"");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#objectExists(java.lang.String)}.
* @throws SQLException
*/
@Test
public void testObjectExistsError() throws SQLException {
ResultSet resultSet = mock(ResultSet.class);
when(ps.executeQuery()).thenReturn(resultSet);
when(resultSet.next()).thenThrow(new SQLException("error"));
handler.objectExists("hello");
verify(plugin).logError(eq("Could not check if key exists in database! hello error"));
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#deleteID(java.lang.String)}.
* @throws SQLException
*/
@Test
public void testDeleteID() throws SQLException {
// Disable plugin
when(plugin.isEnabled()).thenReturn(false);
handler.deleteID("abc123");
verify(ps).execute();
verify(ps).setString(1, "\"abc123\"");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#deleteID(java.lang.String)}.
* @throws SQLException
*/
@Test
public void testDeleteIDError() throws SQLException {
// Disable plugin
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!"));
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#MariaDBDatabaseHandler(world.bentobox.bentobox.BentoBox, java.lang.Class, world.bentobox.bentobox.database.DatabaseConnector)}.
*/
@Test
public void testMariaDBDatabaseHandlerBadPassword() {
when(dbConn.createConnection(any())).thenReturn(null);
new MariaDBDatabaseHandler<>(plugin, Island.class, dbConn);
verify(plugin).logError("Are the settings in config.yml correct?");
verify(pluginManager).disablePlugin(plugin);
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#MariaDBDatabaseHandler(world.bentobox.bentobox.BentoBox, java.lang.Class, world.bentobox.bentobox.database.DatabaseConnector)}.
* @throws SQLException
*/
@Test
public void testMariaDBDatabaseHandlerCreateSchema() 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_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#MariaDBDatabaseHandler(world.bentobox.bentobox.BentoBox, java.lang.Class, world.bentobox.bentobox.database.DatabaseConnector)}.
* @throws SQLException
*/
@Test
public void testMariaDBDatabaseHandlerSchemaFail() throws SQLException {
when(ps.executeUpdate()).thenThrow(new SQLException("oh no!"));
handler = new MariaDBDatabaseHandler<>(plugin, Island.class, dbConn);
verify(plugin).logError("Problem trying to create schema for data object world.bentobox.bentobox.database.objects.Island oh no!");
}
}

View File

@ -1,4 +1,4 @@
package world.bentobox.bentobox.database.mysql; package world.bentobox.bentobox.database.sql.mysql;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -25,6 +25,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl; import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseConnector;
/** /**
* @author tastybento * @author tastybento
@ -72,7 +73,7 @@ public class MySQLDatabaseConnectorTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#MySQLDatabaseConnector(world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseConnector#MySQLDatabaseConnector(world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl)}.
*/ */
@Test @Test
public void testMySQLDatabaseConnector() { public void testMySQLDatabaseConnector() {
@ -83,7 +84,7 @@ public class MySQLDatabaseConnectorTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#createConnection()}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseConnector#createConnection()}.
*/ */
@Ignore("This is apparently very hard to do!") @Ignore("This is apparently very hard to do!")
@Test @Test
@ -93,7 +94,7 @@ public class MySQLDatabaseConnectorTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#createConnection()}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseConnector#createConnection()}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -106,7 +107,7 @@ public class MySQLDatabaseConnectorTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#getConnectionUrl()}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseConnector#getConnectionUrl()}.
*/ */
@Test @Test
public void testGetConnectionUrl() { public void testGetConnectionUrl() {
@ -116,7 +117,7 @@ public class MySQLDatabaseConnectorTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#getUniqueId(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseConnector#getUniqueId(java.lang.String)}.
*/ */
@Test @Test
public void testGetUniqueId() { public void testGetUniqueId() {
@ -125,7 +126,7 @@ public class MySQLDatabaseConnectorTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#uniqueIdExists(java.lang.String, java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseConnector#uniqueIdExists(java.lang.String, java.lang.String)}.
*/ */
@Test @Test
public void testUniqueIdExists() { public void testUniqueIdExists() {
@ -133,7 +134,7 @@ public class MySQLDatabaseConnectorTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#closeConnection()}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseConnector#closeConnection()}.
*/ */
@Test @Test
public void testCloseConnection() { public void testCloseConnection() {
@ -143,7 +144,7 @@ public class MySQLDatabaseConnectorTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#closeConnection()}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseConnector#closeConnection()}.
*/ */
@Test @Test
public void testCloseConnectionError() throws SQLException { public void testCloseConnectionError() throws SQLException {

View File

@ -1,4 +1,4 @@
package world.bentobox.bentobox.database.mysql; package world.bentobox.bentobox.database.sql.mysql;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -22,6 +22,7 @@ import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
@ -33,6 +34,8 @@ import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseConnector;
import world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler;
import world.bentobox.bentobox.util.Util; import world.bentobox.bentobox.util.Util;
/** /**
@ -112,7 +115,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#loadObjects()}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#loadObjects()}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -123,7 +126,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#loadObjects()}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#loadObjects()}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -140,7 +143,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#loadObjects()}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#loadObjects()}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -157,7 +160,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#loadObjects()}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#loadObjects()}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -175,7 +178,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#loadObject(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#loadObject(java.lang.String)}.
*/ */
@Test @Test
public void testLoadObjectNoConnection() throws SQLException { public void testLoadObjectNoConnection() throws SQLException {
@ -185,7 +188,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#loadObject(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#loadObject(java.lang.String)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -203,7 +206,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#loadObject(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#loadObject(java.lang.String)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -218,7 +221,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#loadObject(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#loadObject(java.lang.String)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -233,16 +236,16 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#saveObject(java.lang.Object)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#saveObject(java.lang.Object)}.
*/ */
@Test @Test
public void testSaveObjectNull() { public void testSaveObjectNull() {
handler.saveObject(null); handler.saveObject(null);
verify(plugin).logError(eq("MySQL database request to store a null. ")); verify(plugin).logError(eq("SQL database request to store a null. "));
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#saveObject(java.lang.Object)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#saveObject(java.lang.Object)}.
*/ */
@Test @Test
public void testSaveObjectNotDataObject() { public void testSaveObjectNotDataObject() {
@ -253,10 +256,11 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#saveObject(java.lang.Object)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#saveObject(java.lang.Object)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@Ignore("Async cannot be tested")
public void testSaveObject() throws SQLException { public void testSaveObject() throws SQLException {
// Disable plugin // Disable plugin
when(plugin.isEnabled()).thenReturn(false); when(plugin.isEnabled()).thenReturn(false);
@ -284,10 +288,11 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#saveObject(java.lang.Object)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#saveObject(java.lang.Object)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@Ignore("Async cannot be tested")
public void testSaveObjectFail() throws SQLException { public void testSaveObjectFail() throws SQLException {
// Disable plugin // Disable plugin
when(plugin.isEnabled()).thenReturn(false); when(plugin.isEnabled()).thenReturn(false);
@ -298,16 +303,16 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#deleteObject(java.lang.Object)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#deleteObject(java.lang.Object)}.
*/ */
@Test @Test
public void testDeleteObjectNull() { public void testDeleteObjectNull() {
handler.deleteObject(null); handler.deleteObject(null);
verify(plugin).logError(eq("MySQL database request to delete a null.")); verify(plugin).logError(eq("SQL database request to delete a null."));
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#deleteObject(java.lang.Object)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#deleteObject(java.lang.Object)}.
*/ */
@Test @Test
public void testDeleteObjectIncorrectType() { public void testDeleteObjectIncorrectType() {
@ -318,10 +323,11 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#deleteObject(java.lang.Object)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#deleteObject(java.lang.Object)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@Ignore("Async cannot be tested")
public void testDeleteObject() throws SQLException { public void testDeleteObject() throws SQLException {
// Disable plugin // Disable plugin
when(plugin.isEnabled()).thenReturn(false); when(plugin.isEnabled()).thenReturn(false);
@ -331,7 +337,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#objectExists(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#objectExists(java.lang.String)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -346,7 +352,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#objectExists(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#objectExists(java.lang.String)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -362,7 +368,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#objectExists(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#objectExists(java.lang.String)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -378,7 +384,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#objectExists(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#objectExists(java.lang.String)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@ -391,10 +397,11 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#deleteID(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#deleteID(java.lang.String)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@Ignore("Cannot test async")
public void testDeleteID() throws SQLException { public void testDeleteID() throws SQLException {
// Disable plugin // Disable plugin
when(plugin.isEnabled()).thenReturn(false); when(plugin.isEnabled()).thenReturn(false);
@ -404,10 +411,11 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#deleteID(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#deleteID(java.lang.String)}.
* @throws SQLException * @throws SQLException
*/ */
@Test @Test
@Ignore("Cannot test async")
public void testDeleteIDError() throws SQLException { public void testDeleteIDError() throws SQLException {
// Disable plugin // Disable plugin
when(plugin.isEnabled()).thenReturn(false); when(plugin.isEnabled()).thenReturn(false);
@ -417,7 +425,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#MySQLDatabaseHandler(world.bentobox.bentobox.BentoBox, java.lang.Class, world.bentobox.bentobox.database.DatabaseConnector)}. * Test method for {@link world.bentobox.bentobox.database.sql.mysql.MySQLDatabaseHandler#MySQLDatabaseHandler(world.bentobox.bentobox.BentoBox, java.lang.Class, world.bentobox.bentobox.database.DatabaseConnector)}.
*/ */
@Test @Test
public void testMySQLDatabaseHandlerBadPassword() { public void testMySQLDatabaseHandlerBadPassword() {
@ -428,7 +436,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#MySQLDatabaseHandler(world.bentobox.bentobox.BentoBox, java.lang.Class, world.bentobox.bentobox.database.DatabaseConnector)}. * 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 * @throws SQLException
*/ */
@Test @Test
@ -438,7 +446,7 @@ public class MySQLDatabaseHandlerTest {
} }
/** /**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#MySQLDatabaseHandler(world.bentobox.bentobox.BentoBox, java.lang.Class, world.bentobox.bentobox.database.DatabaseConnector)}. * 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 * @throws SQLException
*/ */
@Test @Test