Added SQLite database (#791)

* Added SQLite database
#570

* Makes SQLite work. Added config.yml option.

Tested on SQLite 3.24.0 2018-06-04 14:10:15
95fbac39baaab1c3a84fdfc82ccb7f42398b2e92f18a2a57bce1d4a713cbaapl

* Fix mariaDB test for close.

* Added test to MySQLDatabaseConnector and Handler
This commit is contained in:
Florian CUNY 2019-06-28 08:33:43 +02:00 committed by GitHub
parent a8655aa669
commit e7133b2176
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 472 additions and 31 deletions

View File

@ -38,7 +38,7 @@ public class Settings implements ConfigObject {
private boolean useEconomy = true;
// Database
@ConfigComment("JSON, MYSQL, MARIADB (10.2.3+), MONGODB, and YAML(deprecated).")
@ConfigComment("JSON, MYSQL, MARIADB (10.2.3+), MONGODB, SQLITE and YAML(deprecated).")
@ConfigComment("Transition database options are:")
@ConfigComment(" YAML2JSON, YAML2MARIADB, YAML2MYSQL")
@ConfigComment(" JSON2MARIADB, JSON2MYSQL, MYSQL2JSON")

View File

@ -5,6 +5,7 @@ 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.mysql.MySQLDatabase;
import world.bentobox.bentobox.database.sqlite.SQLiteDatabase;
import world.bentobox.bentobox.database.transition.Json2MariaDBDatabase;
import world.bentobox.bentobox.database.transition.Json2MySQLDatabase;
import world.bentobox.bentobox.database.transition.MySQL2JsonDatabase;
@ -80,7 +81,12 @@ public interface DatabaseSetup {
*/
MARIADB(new MariaDBDatabase()),
MONGODB(new MongoDBDatabase());
MONGODB(new MongoDBDatabase()),
/**
* @since 1.6.0
*/
SQLITE(new SQLiteDatabase());
DatabaseSetup database;

View File

@ -0,0 +1,17 @@
package world.bentobox.bentobox.database.sqlite;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup;
/**
* @since 1.6.0
* @author Poslovitch
*/
public class SQLiteDatabase implements DatabaseSetup {
@Override
public <T> AbstractDatabaseHandler<T> getHandler(Class<T> dataObjectClass) {
return new SQLiteDatabaseHandler<>(BentoBox.getInstance(), dataObjectClass, new SQLiteDatabaseConnector(BentoBox.getInstance()));
}
}

View File

@ -0,0 +1,66 @@
package world.bentobox.bentobox.database.sqlite;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.DatabaseConnector;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* @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";
SQLiteDatabaseConnector(@NonNull BentoBox plugin) {
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
connectionUrl = "jdbc:sqlite:" + dataFolder.getAbsolutePath() + File.separator + "database.db";
}
@Override
public Object createConnection() {
try {
connection = DriverManager.getConnection(connectionUrl);
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not connect to the database! " + e.getMessage());
}
return connection;
}
@Override
public void closeConnection() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not close SQLite database connection");
}
}
}
@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;
}
}

View File

@ -0,0 +1,215 @@
package world.bentobox.bentobox.database.sqlite;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.DatabaseConnector;
import world.bentobox.bentobox.database.json.AbstractJSONDatabaseHandler;
import world.bentobox.bentobox.database.objects.DataObject;
import 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;
/**
* @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();
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();
}
@Override
public void deleteID(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());
}
}
}

View File

@ -0,0 +1,5 @@
/**
* Contains SQLite database managers.
* @since 1.6.0
*/
package world.bentobox.bentobox.database.sqlite;

View File

@ -11,7 +11,7 @@ general:
# If there is no economy plugin present anyway, money will be automatically disabled.
use-economy: true
database:
# JSON, MYSQL, MARIADB (10.2.3+), MONGODB, and YAML(deprecated).
# JSON, MYSQL, MARIADB (10.2.3+), MONGODB, SQLITE and YAML(deprecated).
# Transition database options are:
# YAML2JSON, YAML2MARIADB, YAML2MYSQL
# JSON2MARIADB, JSON2MYSQL, MYSQL2JSON

View File

@ -401,22 +401,9 @@ public class MariaDBDatabaseHandlerTest {
@Test
public void testClose() throws SQLException {
handler.close();
verify(connection).close();
verify(dbConn).closeConnection();
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#close()}.
* @throws SQLException
*/
@Ignore("it doesn't recognize the #close() ran in the database connector")
@Test
public void testCloseError() throws SQLException {
Mockito.doThrow(new SQLException("error")).when(connection).close();
handler.close();
verify(plugin).logError(eq("Could not close database for some reason"));
}
/**
* Test method for {@link world.bentobox.bentobox.database.mariadb.MariaDBDatabaseHandler#deleteID(java.lang.String)}.
* @throws SQLException

View File

@ -0,0 +1,158 @@
/**
*
*/
package world.bentobox.bentobox.database.mysql;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
/**
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest( { Bukkit.class, DriverManager.class })
public class MySQLDatabaseConnectorTest {
@Mock
private DatabaseConnectionSettingsImpl dbSettings;
@Mock
private Connection connection;
@Mock
private Logger logger;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
when(dbSettings.getDatabaseName()).thenReturn("bentobox");
when(dbSettings.getHost()).thenReturn("localhost");
when(dbSettings.getPort()).thenReturn(1234);
when(dbSettings.getUsername()).thenReturn("username");
when(dbSettings.getPassword()).thenReturn("password");
mockStatic(DriverManager.class);
when(DriverManager.getConnection(
"jdbc:mysql://localhost:1234/bentobox?autoReconnect=true&useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8",
"username",
"password"
)).thenReturn(connection);
// Logger
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getLogger()).thenReturn(logger);
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
}
/**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#MySQLDatabaseConnector(world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl)}.
*/
@Test
public void testMySQLDatabaseConnector() {
new MySQLDatabaseConnector(dbSettings);
verify(dbSettings).getDatabaseName();
verify(dbSettings).getHost();
verify(dbSettings).getPort();
}
/**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#createConnection()}.
*/
@Ignore("This is apparently very hard to do!")
@Test
public void testCreateConnection() {
MySQLDatabaseConnector dc = new MySQLDatabaseConnector(dbSettings);
assertEquals(connection, dc.createConnection());
}
/**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#createConnection()}.
* @throws SQLException
*/
@Test
public void testCreateConnectionError() throws SQLException {
PowerMockito.doThrow(new SQLException("error")).when(DriverManager.class);
DriverManager.getConnection(any(), any(), any());
MySQLDatabaseConnector dc = new MySQLDatabaseConnector(dbSettings);
dc.createConnection();
verify(logger).severe("Could not connect to the database! No suitable driver found for jdbc:mysql://localhost:1234/bentobox?autoReconnect=true&useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8");
}
/**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#getConnectionUrl()}.
*/
@Test
public void testGetConnectionUrl() {
MySQLDatabaseConnector dc = new MySQLDatabaseConnector(dbSettings);
assertEquals("jdbc:mysql://localhost:1234/bentobox"
+ "?autoReconnect=true&useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8", dc.getConnectionUrl());
}
/**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#getUniqueId(java.lang.String)}.
*/
@Test
public void testGetUniqueId() {
assertTrue(new MySQLDatabaseConnector(dbSettings).getUniqueId("any").isEmpty());
}
/**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#uniqueIdExists(java.lang.String, java.lang.String)}.
*/
@Test
public void testUniqueIdExists() {
assertFalse(new MySQLDatabaseConnector(dbSettings).uniqueIdExists("", ""));
}
/**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#closeConnection()}.
*/
@Test
public void testCloseConnection() {
MySQLDatabaseConnector dc = new MySQLDatabaseConnector(dbSettings);
dc.createConnection();
dc.closeConnection();
}
/**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseConnector#closeConnection()}.
*/
@Test
public void testCloseConnectionError() throws SQLException {
MySQLDatabaseConnector dc = new MySQLDatabaseConnector(dbSettings);
dc.createConnection();
dc.closeConnection();
}
}

View File

@ -398,22 +398,9 @@ public class MySQLDatabaseHandlerTest {
@Test
public void testClose() throws SQLException {
handler.close();
verify(connection).close();
verify(dbConn).closeConnection();
}
/**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#close()}.
* @throws SQLException
*/
@Ignore("it doesn't recognize the #close() ran in the database connector")
@Test
public void testCloseError() throws SQLException {
Mockito.doThrow(new SQLException("error")).when(connection).close();
handler.close();
verify(plugin).logError(eq("Could not close database for some reason"));
}
/**
* Test method for {@link world.bentobox.bentobox.database.mysql.MySQLDatabaseHandler#deleteID(java.lang.String)}.
* @throws SQLException