Fix database converter, fix uuid data support. Adds test for database

This commit is contained in:
ceze88 2023-06-29 16:04:12 +02:00
parent 5d0b0409da
commit 84c7d1da4b
6 changed files with 625 additions and 71 deletions

View File

@ -272,21 +272,12 @@ public abstract class SongodaPlugin extends JavaPlugin {
* @param migrations List of migrations to run.
*/
protected void initDatabase(List<DataMigration> migrations) {
boolean legacy = this.config.contains("MySQL");
boolean isSQLite = !this.config.getBoolean("MySQL.Enabled", false);
if (legacy && isSQLite) {
this.config.set("MySQL", null);
File databaseFile = new File(getDataFolder(), getName().toLowerCase() + ".db");
boolean legacy = databaseFile.exists();
if (legacy) {
getLogger().warning("SQLite detected, converting to H2...");
this.dataManager = new DataManager(this, migrations, DatabaseType.SQLITE);
} else if (legacy) {
//Copy creditental from old config to new config
this.databaseConfig.set("MySQL.Hostname", this.config.getString("MySQL.Hostname", "localhost"));
this.databaseConfig.set("MySQL.Port", this.config.getInt("MySQL.Port", 3306));
this.databaseConfig.set("MySQL.Database", this.config.getString("MySQL.Database", "database"));
this.databaseConfig.set("MySQL.Username", this.config.getString("MySQL.Username", "username"));
this.databaseConfig.set("MySQL.Password", this.config.getString("MySQL.Password", "password"));
this.databaseConfig.set("MySQL.Pool Size", this.config.getInt("MySQL.Pool Size", 5));
this.databaseConfig.set("MySQL.Use SSL", this.config.getBoolean("MySQL.Use SSL", false));
this.dataManager = new DataManager(this, migrations);
} else {
this.dataManager = new DataManager(this, migrations);
}
@ -294,10 +285,14 @@ public abstract class SongodaPlugin extends JavaPlugin {
//Check if the type is SQLite
if (dataManager.getDatabaseConnector().getType() == DatabaseType.SQLITE) {
//Let's convert it to H2
DataManager newDataManager = DataMigration.convert(this, DatabaseType.H2);
if (newDataManager != null && newDataManager.getDatabaseConnector().isInitialized()) {
//Set the new data manager
setDataManager(newDataManager);
try {
DataManager newDataManager = DataMigration.convert(this, DatabaseType.H2);
if (newDataManager != null && newDataManager.getDatabaseConnector().isInitialized()) {
//Set the new data manager
setDataManager(newDataManager);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@ -312,7 +307,8 @@ public abstract class SongodaPlugin extends JavaPlugin {
if (this.dataManager == dataManager) return;
//Make sure to shut down the old data manager.
if (this.dataManager != null) {
dataManager.shutdown();
this.dataManager.shutdown();
this.dataManager = null;
}
this.dataManager = dataManager;
}

View File

@ -5,6 +5,8 @@ import com.craftaro.core.SongodaPlugin;
import com.craftaro.core.configuration.Config;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jooq.Condition;
import org.jooq.Field;
import org.jooq.Query;
import org.jooq.Record;
import org.jooq.impl.DSL;
@ -19,11 +21,13 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
@ -47,6 +51,22 @@ public class DataManager {
@Deprecated
private static final Map<String, LinkedList<Runnable>> queues = new HashMap<>();
DataManager() {
this.databaseConfig = null;
this.plugin = null;
this.migrations = Collections.emptyList();
this.databaseConnector = new H2Connector();
}
DataManager(DatabaseType type) {
this.databaseConfig = null;
this.plugin = null;
this.migrations = Collections.emptyList();
this.databaseConnector = new SQLiteConnector();
}
public DataManager(SongodaPlugin plugin, List<DataMigration> migrations) {
this.plugin = plugin;
this.migrations = migrations;
@ -77,15 +97,7 @@ public class DataManager {
break;
}
case "SQLITE": {
//Lets check if we have the sqlite file in the plugin folder
File databaseFile = new File(plugin.getDataFolder(), plugin.getName().toLowerCase()+".db");
if (!databaseFile.exists()) {
//Lets start SQLite and it will be converted to H2
this.databaseConnector = new SQLiteConnector(plugin);
} else {
//No need for conversion, lets use H2 instead
this.databaseConnector = new H2Connector(plugin);
}
this.databaseConnector = new SQLiteConnector(plugin);
break;
}
default: {
@ -116,6 +128,9 @@ public class DataManager {
* @return the prefix to be used by all table names
*/
public String getTablePrefix() {
if (this.plugin == null) {
return "";
}
return this.plugin.getDescription().getName().toLowerCase() + '_';
}
@ -226,7 +241,7 @@ public class DataManager {
databaseConnector.connectDSL(context -> {
context.insertInto(DSL.table(getTablePrefix() + data.getTableName()))
.set(data.serialize())
.onConflict(DSL.field("id")).doUpdate()
.onConflict(data.getId() != -1 ? DSL.field("id") : DSL.field("uuid")).doUpdate()
.set(data.serialize())
.where(data.getId() != -1 ? DSL.field("id").eq(data.getId()) : DSL.field("uuid").eq(data.getUniqueId().toString()))
.execute();
@ -241,7 +256,7 @@ public class DataManager {
databaseConnector.connectDSL(context -> {
context.insertInto(DSL.table(getTablePrefix() + data.getTableName()))
.set(data.serialize())
.onConflict(DSL.field("id")).doUpdate()
.onConflict(data.getId() != -1 ? DSL.field("id") : DSL.field("uuid")).doUpdate()
.set(data.serialize())
.where(data.getId() != -1 ? DSL.field("id").eq(data.getId()) : DSL.field("uuid").eq(data.getUniqueId().toString()))
.execute();
@ -258,7 +273,7 @@ public class DataManager {
for (Data data : dataBatch) {
queries.add(context.insertInto(DSL.table(getTablePrefix() + data.getTableName()))
.set(data.serialize())
.onConflict(DSL.field("id")).doUpdate()
.onConflict(data.getId() != -1 ? DSL.field("id") : DSL.field("uuid")).doUpdate()
.set(data.serialize())
.where(data.getId() != -1 ? DSL.field("id").eq(data.getId()) : DSL.field("uuid").eq(data.getUniqueId().toString())));
}
@ -277,7 +292,7 @@ public class DataManager {
for (Data data : dataBatch) {
queries.add(context.insertInto(DSL.table(getTablePrefix() + data.getTableName()))
.set(data.serialize())
.onConflict(DSL.field("id")).doUpdate()
.onConflict(data.getId() != -1 ? DSL.field("id") : DSL.field("uuid")).doUpdate()
.set(data.serialize())
.where(data.getId() != -1 ? DSL.field("id").eq(data.getId()) : DSL.field("uuid").eq(data.getUniqueId().toString())));
}
@ -308,19 +323,25 @@ public class DataManager {
public <T extends Data> T load(int id, Class<?> clazz, String table) {
try {
AtomicReference<Data> data = new AtomicReference<>((Data) clazz.getConstructor().newInstance());
AtomicBoolean found = new AtomicBoolean(false);
databaseConnector.connectDSL(context -> {
try {
data.set((Data) clazz.getDeclaredConstructor().newInstance());
data.get().deserialize(Objects.requireNonNull(context.select()
.from(DSL.table(getTablePrefix() + table))
.where(DSL.field("id").eq(id))
.fetchOne())
.intoMap());
found.set(true);
} catch (NullPointerException ignored) {
} catch (Exception ex) {
ex.printStackTrace();
}
});
return (T) data.get();
if (found.get()) {
return (T) data.get();
} else {
return null;
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
@ -329,6 +350,8 @@ public class DataManager {
/**
* Loads the data from the database
* @param uuid The uuid of the data
* @param clazz The class of the data
* @param table The table of the data without prefix
* @return The loaded data
*/
@SuppressWarnings("unchecked")
@ -346,7 +369,42 @@ public class DataManager {
.intoMap());
found.set(true);
} catch (NullPointerException ignored) {
} catch (Exception ex) {
ex.printStackTrace();
}
});
if (found.get()) {
return (T) data.get();
} else {
return null;
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Loads the data from the database
* @param uuid The uuid of the data
* @param clazz The class of the data
* @param table The table of the data without prefix
* @param uuidColumn The column of the uuid
* @return The loaded data
*/
@SuppressWarnings("unchecked")
public <T extends Data> T load(UUID uuid, Class<?> clazz, String table, String uuidColumn) {
try {
AtomicReference<Data> data = new AtomicReference<>((Data) clazz.getConstructor().newInstance());
AtomicBoolean found = new AtomicBoolean(false);
databaseConnector.connectDSL(context -> {
try {
data.get().deserialize(Objects.requireNonNull(context.select()
.from(DSL.table(table))
.where(DSL.field(uuidColumn).eq(uuid.toString()))
.fetchOne())
.intoMap());
found.set(true);
} catch (NullPointerException ignored) {
} catch (Exception ex) {
ex.printStackTrace();
}

View File

@ -5,9 +5,13 @@ import com.craftaro.core.SongodaPlugin;
import java.io.File;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -33,7 +37,7 @@ public abstract class DataMigration {
* @param toType The new database type
* @return The new data manager instance
*/
public static DataManager convert(SongodaPlugin plugin, DatabaseType toType) {
public static DataManager convert(SongodaPlugin plugin, DatabaseType toType) throws Exception {
DataManager from = plugin.getDataManager();
if (from.getDatabaseConnector().getType() == toType) {
plugin.getLogger().severe("Cannot convert to the same database type!");
@ -45,65 +49,117 @@ public abstract class DataMigration {
return null;
}
plugin.getLogger().info("Converting data from " + from.getDatabaseConnector().getType().name() + " to " + toType.name() + "...");
DatabaseConnector fromConnector = from.getDatabaseConnector();
DatabaseConnector toConnector = to.getDatabaseConnector();
Connection fromConnection;
Connection toConnection = null;
Connection fromConnection = fromConnector.getConnection();
Connection toConnection = toConnector.getConnection();
try {
fromConnection = fromConnector.getConnection();
toConnection = toConnector.getConnection();
toConnection.setAutoCommit(false);
// Retrieve the list of tables from the old database
List<String> tableNames = new ArrayList<>();
try (ResultSet rs = fromConnection.getMetaData().getTables(null, null, null, new String[] {"TABLE"})) {
while (rs.next()) {
String tableName = rs.getString("TABLE_NAME");
tableNames.add(tableName);
}
}
// Export schema
DatabaseMetaData meta = fromConnection.getMetaData();
ResultSet tables = meta.getTables(null, null, null, new String[]{"TABLE"});
// Transfer the data from the old database to the new database
for (String tableName : tableNames) {
try (
PreparedStatement fromStmt = fromConnection.prepareStatement("SELECT * FROM " + tableName);
ResultSet rs = fromStmt.executeQuery();
PreparedStatement toStmt = toConnection.prepareStatement("INSERT INTO " + tableName + " VALUES (" + String.join(",", Collections.nCopies(rs.getMetaData().getColumnCount(), "?")) + ")")
) {
while (rs.next()) {
for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
toStmt.setObject(i, rs.getObject(i));
}
toStmt.executeUpdate();
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
Statement stmt = fromConnection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName);
ResultSetMetaData metaRs = rs.getMetaData();
int columnCount = metaRs.getColumnCount();
StringBuilder createTableQuery = new StringBuilder();
createTableQuery.append("CREATE TABLE IF NOT EXISTS ").append(tableName).append(" (");
for (int i = 1; i <= columnCount; i++) {
String columnName = metaRs.getColumnName(i);
String columnType = metaRs.getColumnTypeName(i);
int columnSize = metaRs.getColumnDisplaySize(i);
createTableQuery.append(columnName).append(" ").append(columnType).append("(").append(columnSize).append(")");
if (i < columnCount) {
createTableQuery.append(", ");
}
}
createTableQuery.append(")");
toConnection.createStatement().execute(createTableQuery.toString());
while (rs.next()) {
StringBuilder insertQuery = new StringBuilder();
insertQuery.append("INSERT INTO ").append(tableName).append(" VALUES (");
for (int i = 1; i <= columnCount; i++) {
Object value = rs.getObject(i);
if (value == null) {
insertQuery.append("NULL");
} else if (value instanceof String || value instanceof Timestamp) {
insertQuery.append("'").append(value).append("'");
} else {
insertQuery.append(value);
}
if (i < columnCount) {
insertQuery.append(", ");
}
}
insertQuery.append(")");
toConnection.createStatement().execute(insertQuery.toString());
}
}
toConnection.commit();
plugin.getLogger().info("Successfully migrated data from " + from.getDatabaseConnector().getType() + " to " + to.getDatabaseConnector().getType());
} catch (Exception e) {
if (toConnection != null)
if (toConnection != null) {
try {
toConnection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
SongodaCore.getInstance().getLogger().severe("Failed to rollback data for the new database");
plugin.getLogger().severe("Failed to rollback data for the new database");
}
}
e.printStackTrace();
SongodaCore.getInstance().getLogger().severe("Failed to migrate data from " + from.getDatabaseConnector().getType() + " to " + to.getDatabaseConnector().getType());
plugin.getLogger().severe("Failed to migrate data from " + from.getDatabaseConnector().getType() + " to " + to.getDatabaseConnector().getType());
return null;
} finally {
SongodaCore.getInstance().getLogger().info("Successfully migrated data from " + from.getDatabaseConnector().getType() + " to " + to.getDatabaseConnector().getType());
}
fromConnector.closeConnection();
//Get rid of the old database file
//Get rid of the old SQLite database file if it exists and create a backup
File databaseFile = new File(plugin.getDataFolder(), plugin.getName().toLowerCase()+".db");
if (databaseFile.exists()) {
//rename it to .old
databaseFile.renameTo(new File(plugin.getDataFolder(), plugin.getName().toLowerCase()+".old"));
databaseFile.renameTo(new File(plugin.getDataFolder(), plugin.getName().toLowerCase() + ".db.old"));
plugin.getLogger().info("Old database file renamed to " + plugin.getName().toLowerCase() + ".db.old");
}
return to;
}
private String getTableColumns(Connection connection, String tableName) {
StringBuilder columns = new StringBuilder();
try {
DatabaseMetaData meta = connection.getMetaData();
ResultSet rs = meta.getColumns(null, null, tableName, null);
while (rs.next()) {
String columnName = rs.getString("COLUMN_NAME");
String columnType = rs.getString("TYPE_NAME");
columns.append(columnName).append(" ").append(columnType).append(", ");
}
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
columns.setLength(columns.length() - 2);
return columns.toString();
}
}

View File

@ -18,6 +18,29 @@ public class H2Connector implements DatabaseConnector {
private HikariDataSource hikari;
private boolean initializedSuccessfully;
H2Connector() {
this.plugin = null;
int poolSize = 2;
String password = "password";
String username = "username";
HikariConfig config = new HikariConfig();
config.setDriverClassName("org.h2.Driver");
config.setJdbcUrl("jdbc:h2:./db_test/CraftaroCoreTest;AUTO_RECONNECT=TRUE;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE");
config.setPassword(username);
config.setUsername(password);
config.setMaximumPoolSize(poolSize);
try {
this.hikari = new HikariDataSource(config);
this.initializedSuccessfully = true;
} catch (Exception ex) {
ex.printStackTrace();
this.initializedSuccessfully = false;
}
}
public H2Connector(SongodaPlugin plugin) {
this(plugin, plugin.getDatabaseConfig());
}
@ -31,7 +54,7 @@ public class H2Connector implements DatabaseConnector {
HikariConfig config = new HikariConfig();
config.setDriverClassName("com.craftaro.core.third_party.org.h2.Driver");
config.setJdbcUrl("jdbc:h2:./h2_" + plugin.getDataFolder().getPath().replaceAll("\\\\", "/") + "/" + plugin.getDescription().getName().toLowerCase()+ ";AUTO_RECONNECT=TRUE;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE");
config.setJdbcUrl("jdbc:h2:./" + plugin.getDataFolder().getPath().replaceAll("\\\\", "/") + "/" + plugin.getDescription().getName().toLowerCase()+ ";AUTO_RECONNECT=TRUE;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE");
config.setPassword(username);
config.setUsername(password);
config.setMaximumPoolSize(poolSize);
@ -70,7 +93,9 @@ public class H2Connector implements DatabaseConnector {
try (Connection connection = getConnection()) {
return callback.accept(connection);
} catch (Exception ex) {
SongodaCore.getInstance().getLogger().severe("An error occurred executing a MySQL query: " + ex.getMessage());
if (this.plugin != null) {
SongodaCore.getInstance().getLogger().severe("An error occurred executing a MySQL query: " + ex.getMessage());
}
ex.printStackTrace();
}
return OptionalResult.empty();
@ -81,7 +106,9 @@ public class H2Connector implements DatabaseConnector {
try (Connection connection = getConnection()){
callback.accept(DSL.using(connection, SQLDialect.MYSQL));
} catch (Exception ex) {
this.plugin.getLogger().severe("An error occurred executing a MySQL query: " + ex.getMessage());
if (this.plugin != null) {
this.plugin.getLogger().severe("An error occurred executing a MySQL query: " + ex.getMessage());
}
ex.printStackTrace();
}
}
@ -91,7 +118,9 @@ public class H2Connector implements DatabaseConnector {
try (Connection connection = getConnection()) {
return callback.accept(DSL.using(connection, SQLDialect.MYSQL));
} catch (Exception ex) {
SongodaCore.getInstance().getLogger().severe("An error occurred executing a MySQL query: " + ex.getMessage());
if (this.plugin != null) {
SongodaCore.getInstance().getLogger().severe("An error occurred executing a MySQL query: " + ex.getMessage());
}
ex.printStackTrace();
}
return OptionalResult.empty();

View File

@ -1,6 +1,7 @@
package com.craftaro.core.database;
import com.craftaro.core.SongodaCore;
import eu.decentsoftware.holograms.api.utils.scheduler.S;
import org.bukkit.plugin.Plugin;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;
@ -16,6 +17,11 @@ public class SQLiteConnector implements DatabaseConnector {
private final String connectionString;
private Connection connection;
SQLiteConnector() {
this.plugin = null;
this.connectionString = "jdbc:sqlite:" + "."+File.separator+"db_test"+File.separator+"CraftaroCoreTestSQLite.db";
}
public SQLiteConnector(Plugin plugin) {
this.plugin = plugin;
this.connectionString = "jdbc:sqlite:" + plugin.getDataFolder() + File.separator + plugin.getDescription().getName().toLowerCase() + ".db";

View File

@ -0,0 +1,409 @@
package com.craftaro.core.database;
import org.jooq.impl.DSL;
import org.jooq.impl.SQLDataType;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
public class DatabaseTest {
//Create database tests for DataManager
static {
// Disable tips and logo for Jooq
System.setProperty("org.jooq.no-tips", "true");
System.setProperty("org.jooq.no-logo", "true");
}
private static boolean deleteDirectory(File directoryToBeDeleted) {
File[] allContents = directoryToBeDeleted.listFiles();
if (allContents != null) {
for (File file : allContents) {
deleteDirectory(file);
}
}
return directoryToBeDeleted.delete();
}
@Test
public void testId() {
File dbDir = new File("./db_test");
File logsDir = new File("./logs");
DataManager dataManager = new DataManager();
if (!dataManager.getDatabaseConnector().isInitialized()) {
throw new RuntimeException("Database 'Data' test failed - DatabaseConnector not initialized");
}
//Create tables
dataManager.getDatabaseConnector().connectDSL(context -> {
context.createTableIfNotExists("data_test")
.column("id", SQLDataType.INTEGER.identity(true))
.column("name", SQLDataType.VARCHAR(16))
.column("points", SQLDataType.INTEGER)
.column("other_points", SQLDataType.INTEGER)
.constraint(DSL.constraint().primaryKey(DSL.field("id")))
.execute();
});
int id = new Random().nextInt(1000);
DataTestId dataTest = new DataTestId(id, "Test", 10, 20);
dataManager.saveSync(dataTest);
DataTestId dataTest2 = dataManager.load(id, DataTestId.class, "data_test");
if (!dataTest.equals(dataTest2)) {
System.err.println("Database 'Data - ID' test failed");
} else {
System.out.println("Database 'Data - ID' test passed");
}
dataManager.shutdownNow();
if (dbDir.exists()) {
deleteDirectory(dbDir);
}
if (logsDir.exists()) {
deleteDirectory(logsDir);
}
}
@Test
public void testUUID() {
File dbDir = new File("./db_test");
File logsDir = new File("./logs");
DataManager dataManager = new DataManager();
if (!dataManager.getDatabaseConnector().isInitialized()) {
throw new RuntimeException("Database 'Data' test failed - DatabaseConnector not initialized");
}
//Create tables
dataManager.getDatabaseConnector().connectDSL(context -> {
context.createTableIfNotExists("data_uuid_test")
.column("uuid", SQLDataType.VARCHAR(36))
.column("name", SQLDataType.VARCHAR(16))
.column("points", SQLDataType.INTEGER)
.column("other_points", SQLDataType.INTEGER)
.constraint(DSL.constraint().primaryKey(DSL.field("uuid")))
.execute();
});
UUID uuid = UUID.randomUUID();
DataTestUUID dataTest = new DataTestUUID(uuid, "Test", 10, 20);
dataManager.saveSync(dataTest);
DataTestUUID dataTest2 = dataManager.load(uuid, DataTestUUID.class, "data_uuid_test");
if (!dataTest.equals(dataTest2)) {
System.err.println(dataTest);
System.err.println(dataTest2);
System.err.println("Database 'Data - UUID' test failed");
} else {
System.out.println("Database 'Data - UUID' test passed");
}
dataManager.shutdownNow();
if (dbDir.exists()) {
deleteDirectory(dbDir);
}
if (logsDir.exists()) {
deleteDirectory(logsDir);
}
}
@Test
public static void testConvert() {
File dbDir = new File("./db_test");
File logsDir = new File("./logs");
DataManager sqlite = new DataManager(DatabaseType.SQLITE);
DataManager h2 = new DataManager();
if (!sqlite.getDatabaseConnector().isInitialized()) {
throw new RuntimeException("Database 'Data - Convert' test failed - DatabaseConnector not initialized");
}
if (!h2.getDatabaseConnector().isInitialized()) {
throw new RuntimeException("Database 'Data - Convert' test failed - DatabaseConnector not initialized");
}
//Create tables for SQLite
try (Connection sqliteConnection = sqlite.getDatabaseConnector().getConnection(); Connection h2Connection = h2.getDatabaseConnector().getConnection()) {
sqliteConnection.createStatement().execute("CREATE TABLE IF NOT EXISTS `data_convert_test` (`id` INTEGER PRIMARY KEY NOT NULL, `name` VARCHAR(16), `points` INTEGER, `other_points` INTEGER)");
//create 2 other tables for some example data with different column names and types
sqliteConnection.createStatement().execute("CREATE TABLE IF NOT EXISTS `data_convert_test2` (`id` INTEGER PRIMARY KEY NOT NULL, `name` VARCHAR(16), `points` INTEGER, `other_points` INTEGER)");
sqliteConnection.createStatement().execute("CREATE TABLE IF NOT EXISTS `data_convert_test3` (`id` INTEGER PRIMARY KEY NOT NULL, `name` VARCHAR(16), `points` INTEGER, `other_points` INTEGER)");
//Fill with some example data using loops
for (int i = 0; i < 10; i++) {
sqliteConnection.createStatement().execute("INSERT INTO `data_convert_test` (`name`, `points`, `other_points`) VALUES ('Test" + i + "', " + i + ", " + i + ")");
sqliteConnection.createStatement().execute("INSERT INTO `data_convert_test2` (`name`, `points`, `other_points`) VALUES ('Test" + i + "', " + i + ", " + i + ")");
sqliteConnection.createStatement().execute("INSERT INTO `data_convert_test3` (`name`, `points`, `other_points`) VALUES ('Test" + i + "', " + i + ", " + i + ")");
}
//Migrate tables and data to H2
try {
// Export schema
DatabaseMetaData meta = sqliteConnection.getMetaData();
ResultSet tables = meta.getTables(null, null, null, new String[]{"TABLE"});
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
Statement stmt = sqliteConnection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName);
ResultSetMetaData metaRs = rs.getMetaData();
int columnCount = metaRs.getColumnCount();
StringBuilder createTableQuery = new StringBuilder();
createTableQuery.append("CREATE TABLE ").append(tableName).append(" (");
for (int i = 1; i <= columnCount; i++) {
String columnName = metaRs.getColumnName(i);
String columnType = metaRs.getColumnTypeName(i);
int columnSize = metaRs.getColumnDisplaySize(i);
createTableQuery.append(columnName).append(" ").append(columnType).append("(").append(columnSize).append(")");
if (i < columnCount) {
createTableQuery.append(", ");
}
}
createTableQuery.append(")");
h2Connection.createStatement().execute(createTableQuery.toString());
while (rs.next()) {
StringBuilder insertQuery = new StringBuilder();
insertQuery.append("INSERT INTO ").append(tableName).append(" VALUES (");
for (int i = 1; i <= columnCount; i++) {
Object value = rs.getObject(i);
if (value == null) {
insertQuery.append("NULL");
} else if (value instanceof String || value instanceof Timestamp) {
insertQuery.append("'").append(value).append("'");
} else {
insertQuery.append(value);
}
if (i < columnCount) {
insertQuery.append(", ");
}
}
insertQuery.append(")");
h2Connection.createStatement().execute(insertQuery.toString());
}
}
//Query data from both databases and compare
Statement stmt = sqliteConnection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM `data_convert_test`");
//H2
Statement stmt2 = h2Connection.createStatement();
ResultSet rs2 = stmt2.executeQuery("SELECT * FROM `data_convert_test`");
//Compare data
while (rs.next() && rs2.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int points = rs.getInt("points");
int otherPoints = rs.getInt("other_points");
int id2 = rs2.getInt("id");
String name2 = rs2.getString("name");
int points2 = rs2.getInt("points");
int otherPoints2 = rs2.getInt("other_points");
if (id != id2 || !name.equals(name2) || points != points2 || otherPoints != otherPoints2) {
System.err.println("Database 'Data - Convert' test failed - Data mismatch");
System.err.println("SQLite: " + id + " " + name + " " + points + " " + otherPoints);
System.err.println("H2: " + id2 + " " + name2 + " " + points2 + " " + otherPoints2);
return;
}
}
System.out.println("Database 'Data - Convert' test passed");
} catch (Exception e) {
e.printStackTrace();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
sqlite.shutdownNow();
h2.shutdownNow();
if (dbDir.exists()) {
deleteDirectory(dbDir);
}
if (logsDir.exists()) {
deleteDirectory(logsDir);
}
}
private static String getTableColumns(Connection sqliteConnection, String tableName) {
StringBuilder columns = new StringBuilder();
try {
DatabaseMetaData meta = sqliteConnection.getMetaData();
ResultSet rs = meta.getColumns(null, null, tableName, null);
while (rs.next()) {
String columnName = rs.getString("COLUMN_NAME");
String columnType = rs.getString("TYPE_NAME");
columns.append(columnName).append(" ").append(columnType).append(", ");
}
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
columns.setLength(columns.length() - 2);
return columns.toString();
}
private static class DataTestId implements Data {
private int id;
private String name;
private int points;
private int otherPoints;
public DataTestId() {
}
public DataTestId(int id, String name, int points, int otherPoints) {
this.id = id;
this.name = name;
this.points = points;
this.otherPoints = otherPoints;
}
@Override
public int getId() {
return this.id;
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<>();
map.put("id", this.id);
map.put("name", this.name);
map.put("points", this.points);
map.put("other_points", this.otherPoints);
return map;
}
@Override
public Data deserialize(Map<String, Object> map) {
this.id = (int) map.get("id");
this.name = (String) map.get("name");
this.points = (int) map.get("points");
this.otherPoints = (int) map.get("other_points");
return this;
}
@Override
public String getTableName() {
return "data_test";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof DataTestId)) return false;
DataTestId other = (DataTestId) obj;
return id == other.id && name.equals(other.name) && points == other.points && otherPoints == other.otherPoints;
}
}
private static class DataTestUUID implements Data {
private UUID uuid;
private String name;
private int points;
private int otherPoints;
public DataTestUUID() {
}
public DataTestUUID(UUID uuid, String name, int points, int otherPoints) {
this.uuid = uuid;
this.name = name;
this.points = points;
this.otherPoints = otherPoints;
}
@Override
public UUID getUniqueId() {
return this.uuid;
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<>();
map.put("uuid", this.uuid.toString());
map.put("name", this.name);
map.put("points", this.points);
map.put("other_points", this.otherPoints);
return map;
}
@Override
public Data deserialize(Map<String, Object> map) {
this.uuid = UUID.fromString((String) map.get("uuid"));
this.name = (String) map.get("name");
this.points = (int) map.get("points");
this.otherPoints = (int) map.get("other_points");
return this;
}
@Override
public String getTableName() {
return "data_uuid_test";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof DataTestUUID)) return false;
DataTestUUID other = (DataTestUUID) obj;
return uuid.equals(other.uuid) && name.equals(other.name) && points == other.points && otherPoints == other.otherPoints;
}
@Override
public String toString() {
return "DataTestUUID{" +
"uuid=" + uuid +
", name='" + name + '\'' +
", points=" + points +
", otherPoints=" + otherPoints +
'}';
}
}
}