ORMLite to JDBI

This commit is contained in:
2026-05-28 16:19:20 -04:00
parent 8b2ca5e072
commit 607595e3c2
25 changed files with 290 additions and 434 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ plugins {
} }
dependencies { dependencies {
compileOnlyApi("com.j256.ormlite:ormlite-core:6.1") compileOnlyApi("org.jdbi:jdbi3-core:3.53.0")
api("com.google.code.gson:gson:2.13.2") api("com.google.code.gson:gson:2.13.2")
compileOnly("io.papermc.paper:paper-api:26.1.2.build.+") compileOnly("io.papermc.paper:paper-api:26.1.2.build.+")
@@ -1,22 +0,0 @@
package dev.plex.api.storage;
import com.j256.ormlite.dao.Dao;
import java.sql.SQLException;
/**
* Creates ORMLite DAOs for module-scoped tables.
*/
public interface ModuleOrm
{
/**
* Creates or returns a cached DAO for a module-local table.
*
* @param entityClass ORMLite entity class
* @param localTableName module-local table name
* @param <T> entity type
* @param <ID> entity ID type
* @return ORMLite DAO using the module-prefixed physical table
* @throws SQLException if the DAO cannot be created
*/
<T, ID> Dao<T, ID> dao(Class<T> entityClass, String localTableName) throws SQLException;
}
@@ -1,6 +1,6 @@
package dev.plex.api.storage; package dev.plex.api.storage;
import java.sql.SQLException; import org.jdbi.v3.core.Jdbi;
/** /**
* Module-scoped storage namespace. * Module-scoped storage namespace.
@@ -30,19 +30,10 @@ public interface ModuleStorage
ModuleMigrations migrations(); ModuleMigrations migrations();
/** /**
* Returns module ORMLite DAO operations. * Returns the shared JDBI instance. Build SQL with {@link #table(String)} for
* physical-table resolution; use {@code jdbi().inTransaction(...)} for multi-statement transactions.
* *
* @return module ORMLite DAO operations * @return shared JDBI instance
*/ */
ModuleOrm orm(); Jdbi jdbi();
/**
* Runs work inside a storage transaction.
*
* @param callable work to run
* @param <T> callback result type
* @return callback result
* @throws SQLException if the transaction cannot complete
*/
<T> T transaction(SqlCallable<T> callable) throws SQLException;
} }
@@ -1,20 +0,0 @@
package dev.plex.api.storage;
import java.sql.SQLException;
/**
* Callback used for SQL work that does not receive a connection directly.
*
* @param <T> callback result type
*/
@FunctionalInterface
public interface SqlCallable<T>
{
/**
* Runs SQL work.
*
* @return callback result
* @throws SQLException if the SQL work cannot complete
*/
T call() throws SQLException;
}
+1 -2
View File
@@ -28,8 +28,7 @@ dependencies {
library("org.postgresql:postgresql:42.7.11") library("org.postgresql:postgresql:42.7.11")
library("org.xerial:sqlite-jdbc:3.53.1.0") library("org.xerial:sqlite-jdbc:3.53.1.0")
library("com.zaxxer:HikariCP:7.0.2") library("com.zaxxer:HikariCP:7.0.2")
library("com.j256.ormlite:ormlite-core:6.1") library("org.jdbi:jdbi3-core:3.53.0")
library("com.j256.ormlite:ormlite-jdbc:6.1")
library("org.jetbrains:annotations:26.1.0") library("org.jetbrains:annotations:26.1.0")
compileOnly("io.papermc.paper:paper-api:${paperApiVersion}.build.+") compileOnly("io.papermc.paper:paper-api:${paperApiVersion}.build.+")
compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") { compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") {
+9 -9
View File
@@ -19,8 +19,8 @@ import dev.plex.player.PlexPlayer;
import dev.plex.punishment.PunishmentManager; import dev.plex.punishment.PunishmentManager;
import dev.plex.services.ServiceManager; import dev.plex.services.ServiceManager;
import dev.plex.storage.RedisConnection; import dev.plex.storage.RedisConnection;
import dev.plex.storage.SQLConnection;
import dev.plex.storage.StorageType; import dev.plex.storage.StorageType;
import dev.plex.storage.database.Database;
import dev.plex.storage.player.SQLPlayerData; import dev.plex.storage.player.SQLPlayerData;
import dev.plex.storage.player.PlayerModuleDataRepository; import dev.plex.storage.player.PlayerModuleDataRepository;
import dev.plex.storage.player.SQLPlayerModuleData; import dev.plex.storage.player.SQLPlayerModuleData;
@@ -63,7 +63,7 @@ public class Plex extends JavaPlugin
public Config toggles; public Config toggles;
public File modulesFolder; public File modulesFolder;
private StorageType storageType = StorageType.SQLITE; private StorageType storageType = StorageType.SQLITE;
private SQLConnection sqlConnection; private Database database;
private RedisConnection redisConnection; private RedisConnection redisConnection;
private PlayerCache playerCache; private PlayerCache playerCache;
@@ -146,7 +146,7 @@ public class Plex extends JavaPlugin
// Don't add default entries to these files // Don't add default entries to these files
indefBans.load(false); indefBans.load(false);
sqlConnection = new SQLConnection(this); database = new Database(this);
redisConnection = new RedisConnection(this); redisConnection = new RedisConnection(this);
playerCache = new PlayerCache(); playerCache = new PlayerCache();
@@ -220,10 +220,10 @@ public class Plex extends JavaPlugin
PlexLog.log("Redis is disabled in the configuration file, not connecting."); PlexLog.log("Redis is disabled in the configuration file, not connecting.");
} }
punishmentRepository = new SQLPunishment(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor()); punishmentRepository = new SQLPunishment(database.getJdbi(), api.scheduler().asyncExecutor());
playerRepository = new SQLPlayerData(sqlConnection.getConnectionSource(), punishmentRepository); playerRepository = new SQLPlayerData(database.getJdbi(), punishmentRepository, storageType);
playerModuleDataRepository = new SQLPlayerModuleData(sqlConnection, storageType); playerModuleDataRepository = new SQLPlayerModuleData(database.getJdbi(), storageType);
noteRepository = new SQLNotes(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor()); noteRepository = new SQLNotes(database.getJdbi(), api.scheduler().asyncExecutor());
playerService = new PlayerService(playerCache, playerRepository); playerService = new PlayerService(playerCache, playerRepository);
playerNameResolver = new PlayerNameResolver(playerService); playerNameResolver = new PlayerNameResolver(playerService);
@@ -275,9 +275,9 @@ public class Plex extends JavaPlugin
moduleManager.disableModules(); moduleManager.disableModules();
if (sqlConnection != null) if (database != null)
{ {
sqlConnection.close(); database.close();
} }
} }
@@ -18,7 +18,7 @@ final class DefaultStorageApi implements StorageApi
@Override @Override
public <T> T withConnection(SqlFunction<T> function) throws SQLException public <T> T withConnection(SqlFunction<T> function) throws SQLException
{ {
try (Connection connection = plugin.getSqlConnection().getCon()) try (Connection connection = plugin.getDatabase().getConnection())
{ {
return function.apply(connection); return function.apply(connection);
} }
@@ -1,22 +0,0 @@
package dev.plex.storage;
import dev.plex.Plex;
import dev.plex.storage.database.Database;
/**
* Database bootstrap and connection holder.
*
* <p>The historical name is kept so existing module-facing accessors do not break.</p>
*/
public class SQLConnection extends Database
{
public SQLConnection(Plex plugin)
{
super(plugin);
}
public java.sql.Connection getCon() throws java.sql.SQLException
{
return getConnection();
}
}
@@ -134,6 +134,33 @@ public enum StorageType
}; };
} }
public String playerUpsertSql()
{
return switch (this)
{
case SQLITE, POSTGRES -> """
INSERT INTO players (uuid, last_known_name, login_msg, prefix, staffChat, commandspy)
VALUES (:uuid, :name, :login, :prefix, :staffChat, :commandSpy)
ON CONFLICT(uuid) DO UPDATE SET
last_known_name = excluded.last_known_name,
login_msg = excluded.login_msg,
prefix = excluded.prefix,
staffChat = excluded.staffChat,
commandspy = excluded.commandspy
""";
case MARIADB -> """
INSERT INTO `players` (`uuid`, `last_known_name`, `login_msg`, `prefix`, `staffChat`, `commandspy`)
VALUES (:uuid, :name, :login, :prefix, :staffChat, :commandSpy)
ON DUPLICATE KEY UPDATE
`last_known_name` = VALUES(`last_known_name`),
`login_msg` = VALUES(`login_msg`),
`prefix` = VALUES(`prefix`),
`staffChat` = VALUES(`staffChat`),
`commandspy` = VALUES(`commandspy`)
""";
};
}
public String getDisplayName() public String getDisplayName()
{ {
return displayName; return displayName;
@@ -1,13 +1,11 @@
package dev.plex.storage.database; package dev.plex.storage.database;
import com.j256.ormlite.jdbc.DataSourceConnectionSource;
import com.j256.ormlite.support.ConnectionSource;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import dev.plex.Plex; import dev.plex.Plex;
import dev.plex.storage.StorageType; import dev.plex.storage.StorageType;
import dev.plex.util.PlexLog;
import lombok.Getter; import lombok.Getter;
import org.jdbi.v3.core.Jdbi;
import java.util.List; import java.util.List;
@@ -16,7 +14,7 @@ public class Database
{ {
protected final Plex plugin; protected final Plex plugin;
private final HikariDataSource dataSource; private final HikariDataSource dataSource;
private final ConnectionSource connectionSource; private final Jdbi jdbi;
private final StorageType storageType; private final StorageType storageType;
private final MigrationRunner migrationRunner; private final MigrationRunner migrationRunner;
@@ -39,9 +37,9 @@ public class Database
this.dataSource = new HikariDataSource(config); this.dataSource = new HikariDataSource(config);
try try
{ {
this.connectionSource = new DataSourceConnectionSource(dataSource, config.getJdbcUrl());
this.migrationRunner = new MigrationRunner(storageType); this.migrationRunner = new MigrationRunner(storageType);
this.migrationRunner.runCore(dataSource, getClass().getClassLoader(), List.of("001_initial_schema")); this.migrationRunner.runCore(dataSource, getClass().getClassLoader(), List.of("001_initial_schema"));
this.jdbi = Jdbi.create(dataSource);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -57,14 +55,6 @@ public class Database
public void close() public void close()
{ {
try
{
connectionSource.close();
}
catch (Exception e)
{
PlexLog.warn("Failed to close ORMLite connection source: " + e.getMessage());
}
dataSource.close(); dataSource.close();
} }
} }
@@ -1,31 +1,17 @@
package dev.plex.storage.database.entity; package dev.plex.storage.database.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@Getter @Getter
@Setter @Setter
@DatabaseTable(tableName = "notes")
public class NoteEntity public class NoteEntity
{ {
@DatabaseField(generatedId = true, columnName = "row_id")
private long rowId; private long rowId;
@DatabaseField(columnName = "id", index = true)
private int id; private int id;
@DatabaseField(columnName = "uuid", canBeNull = false, index = true, width = 46)
private String uuid; private String uuid;
@DatabaseField(columnName = "written_by_uuid", width = 46)
private String writtenByUuid; private String writtenByUuid;
@DatabaseField(columnName = "note", width = 2000)
private String note; private String note;
@DatabaseField(columnName = "timestamp")
private long timestamp; private long timestamp;
public NoteEntity() public NoteEntity()
@@ -1,34 +1,17 @@
package dev.plex.storage.database.entity; package dev.plex.storage.database.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@Getter @Getter
@Setter @Setter
@DatabaseTable(tableName = "players")
public class PlayerEntity public class PlayerEntity
{ {
@DatabaseField(id = true, columnName = "uuid", width = 46)
private String uuid; private String uuid;
@DatabaseField(columnName = "last_known_name", width = 18, index = true)
private String lastKnownName; private String lastKnownName;
@DatabaseField(columnName = "login_msg", width = 2000)
private String loginMessage; private String loginMessage;
@DatabaseField(columnName = "prefix", width = 2000)
private String prefix; private String prefix;
@DatabaseField(columnName = "staffChat")
private boolean staffChat; private boolean staffChat;
@DatabaseField(columnName = "ips", width = 2000)
private String ips;
@DatabaseField(columnName = "commandspy")
private boolean commandSpy; private boolean commandSpy;
public PlayerEntity() public PlayerEntity()
@@ -1,22 +1,14 @@
package dev.plex.storage.database.entity; package dev.plex.storage.database.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@Getter @Getter
@Setter @Setter
@DatabaseTable(tableName = "player_ips")
public class PlayerIpEntity public class PlayerIpEntity
{ {
@DatabaseField(generatedId = true, columnName = "id")
private long id; private long id;
@DatabaseField(columnName = "player_uuid", canBeNull = false, index = true, width = 46)
private String playerUuid; private String playerUuid;
@DatabaseField(columnName = "ip", canBeNull = false, index = true, width = 64)
private String ip; private String ip;
public PlayerIpEntity() public PlayerIpEntity()
@@ -1,49 +1,23 @@
package dev.plex.storage.database.entity; package dev.plex.storage.database.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@Getter @Getter
@Setter @Setter
@DatabaseTable(tableName = "punishments")
public class PunishmentEntity public class PunishmentEntity
{ {
@DatabaseField(generatedId = true, columnName = "id")
private long id; private long id;
@DatabaseField(columnName = "punished_uuid", canBeNull = false, index = true, width = 46)
private String punishedUuid; private String punishedUuid;
@DatabaseField(columnName = "punisher_uuid", width = 46)
private String punisherUuid; private String punisherUuid;
@DatabaseField(columnName = "source", width = 20)
private String source; private String source;
@DatabaseField(columnName = "punisher_reference", width = 200)
private String punisherReference; private String punisherReference;
@DatabaseField(columnName = "ip", width = 2000, index = true)
private String ip; private String ip;
@DatabaseField(columnName = "type", width = 30)
private String type; private String type;
@DatabaseField(columnName = "reason", width = 2000)
private String reason; private String reason;
@DatabaseField(columnName = "customTime")
private boolean customTime; private boolean customTime;
@DatabaseField(columnName = "active", index = true)
private boolean active; private boolean active;
@DatabaseField(columnName = "issueDate")
private long issueDate; private long issueDate;
@DatabaseField(columnName = "endDate")
private long endDate; private long endDate;
public PunishmentEntity() public PunishmentEntity()
@@ -29,8 +29,8 @@ public class ServerModuleMigrations implements ModuleMigrations
@Override @Override
public void run(String resourceRoot, List<String> versions) throws SQLException public void run(String resourceRoot, List<String> versions) throws SQLException
{ {
plugin.getSqlConnection().getMigrationRunner().runModule( plugin.getDatabase().getMigrationRunner().runModule(
plugin.getSqlConnection().getDataSource(), plugin.getDatabase().getDataSource(),
module, module,
storage.scope(), storage.scope(),
resourceRoot, resourceRoot,
@@ -1,42 +0,0 @@
package dev.plex.storage.module;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.DatabaseTableConfig;
import dev.plex.api.storage.ModuleOrm;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ServerModuleOrm implements ModuleOrm
{
private final ConnectionSource connectionSource;
private final ServerModuleStorage storage;
private final Map<String, Dao<?, ?>> daos = new ConcurrentHashMap<>();
public ServerModuleOrm(ConnectionSource connectionSource, ServerModuleStorage storage)
{
this.connectionSource = connectionSource;
this.storage = storage;
}
@Override
@SuppressWarnings("unchecked")
public <T, ID> Dao<T, ID> dao(Class<T> entityClass, String localTableName) throws SQLException
{
String key = entityClass.getName() + ":" + localTableName;
Dao<?, ?> existing = daos.get(key);
if (existing != null)
{
return (Dao<T, ID>) existing;
}
DatabaseTableConfig<T> tableConfig = DatabaseTableConfig.fromClass(connectionSource.getDatabaseType(), entityClass);
tableConfig.setTableName(storage.table(localTableName));
Dao<T, ID> dao = DaoManager.createDao(connectionSource, tableConfig);
daos.put(key, dao);
return dao;
}
}
@@ -1,14 +1,10 @@
package dev.plex.storage.module; package dev.plex.storage.module;
import com.j256.ormlite.misc.TransactionManager;
import dev.plex.Plex; import dev.plex.Plex;
import dev.plex.api.storage.ModuleMigrations; import dev.plex.api.storage.ModuleMigrations;
import dev.plex.api.storage.ModuleOrm;
import dev.plex.api.storage.ModuleStorage; import dev.plex.api.storage.ModuleStorage;
import dev.plex.api.storage.SqlCallable;
import dev.plex.module.PlexModule; import dev.plex.module.PlexModule;
import org.jdbi.v3.core.Jdbi;
import java.sql.SQLException;
public class ServerModuleStorage implements ModuleStorage public class ServerModuleStorage implements ModuleStorage
{ {
@@ -16,7 +12,6 @@ public class ServerModuleStorage implements ModuleStorage
private final PlexModule module; private final PlexModule module;
private final String prefix; private final String prefix;
private final ModuleMigrations migrations; private final ModuleMigrations migrations;
private final ModuleOrm orm;
public ServerModuleStorage(Plex plugin, PlexModule module) public ServerModuleStorage(Plex plugin, PlexModule module)
{ {
@@ -24,7 +19,6 @@ public class ServerModuleStorage implements ModuleStorage
this.module = module; this.module = module;
this.prefix = ModuleNames.prefix(module); this.prefix = ModuleNames.prefix(module);
this.migrations = new ServerModuleMigrations(plugin, module, this); this.migrations = new ServerModuleMigrations(plugin, module, this);
this.orm = new ServerModuleOrm(plugin.getSqlConnection().getConnectionSource(), this);
} }
@Override @Override
@@ -56,14 +50,8 @@ public class ServerModuleStorage implements ModuleStorage
} }
@Override @Override
public ModuleOrm orm() public Jdbi jdbi()
{ {
return orm; return plugin.getDatabase().getJdbi();
}
@Override
public <T> T transaction(SqlCallable<T> callable) throws SQLException
{
return TransactionManager.callInTransaction(plugin.getSqlConnection().getConnectionSource(), callable::call);
} }
} }
@@ -1,54 +1,44 @@
package dev.plex.storage.player; package dev.plex.storage.player;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.stmt.DeleteBuilder;
import dev.plex.player.PlexPlayer; import dev.plex.player.PlexPlayer;
import dev.plex.storage.StorageType;
import dev.plex.storage.database.entity.PlayerEntity; import dev.plex.storage.database.entity.PlayerEntity;
import dev.plex.storage.database.entity.PlayerIpEntity;
import dev.plex.storage.repository.PlayerRepository; import dev.plex.storage.repository.PlayerRepository;
import dev.plex.storage.repository.PunishmentRepository; import dev.plex.storage.repository.PunishmentRepository;
import dev.plex.util.PlexLog;
import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.JdbiException;
import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
/** /**
* Player persistence backed by ORMLite. * Player persistence backed by JDBI.
*/ */
public class SQLPlayerData implements PlayerRepository public class SQLPlayerData implements PlayerRepository
{ {
private static final Gson GSON = new Gson(); private final Jdbi jdbi;
private final Dao<PlayerEntity, String> players;
private final Dao<PlayerIpEntity, Long> playerIps;
private final PunishmentRepository punishmentRepository; private final PunishmentRepository punishmentRepository;
private final StorageType storageType;
public SQLPlayerData(ConnectionSource connectionSource, PunishmentRepository punishmentRepository) public SQLPlayerData(Jdbi jdbi, PunishmentRepository punishmentRepository, StorageType storageType)
{ {
this.jdbi = jdbi;
this.punishmentRepository = punishmentRepository; this.punishmentRepository = punishmentRepository;
try this.storageType = storageType;
{
this.players = DaoManager.createDao(connectionSource, PlayerEntity.class);
this.playerIps = DaoManager.createDao(connectionSource, PlayerIpEntity.class);
}
catch (SQLException e)
{
throw new IllegalStateException("Failed to create player DAOs", e);
}
} }
public boolean exists(UUID uuid) public boolean exists(UUID uuid)
{ {
try try
{ {
return players.idExists(uuid.toString()); return jdbi.withHandle(h -> h.createQuery("SELECT 1 FROM players WHERE uuid = :u")
.bind("u", uuid.toString()).mapTo(Integer.class).findFirst().isPresent());
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to check player existence for {0}: {1}", uuid, e.getMessage());
return false; return false;
} }
} }
@@ -57,11 +47,12 @@ public class SQLPlayerData implements PlayerRepository
{ {
try try
{ {
return players.queryBuilder().where().eq("last_known_name", username).queryForFirst() != null; return jdbi.withHandle(h -> h.createQuery("SELECT 1 FROM players WHERE last_known_name = :n")
.bind("n", username).mapTo(Integer.class).findFirst().isPresent());
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to check player existence for {0}: {1}", username, e.getMessage());
return false; return false;
} }
} }
@@ -70,11 +61,16 @@ public class SQLPlayerData implements PlayerRepository
{ {
try try
{ {
return toPlayer(players.queryForId(uuid.toString()), loadExtraData); return jdbi.withHandle(h ->
}
catch (SQLException e)
{ {
e.printStackTrace(); PlayerEntity e = h.createQuery("SELECT * FROM players WHERE uuid = :u")
.bind("u", uuid.toString()).map((rs, ctx) -> mapRow(rs)).findFirst().orElse(null);
return toPlayer(h, e, loadExtraData);
});
}
catch (JdbiException e)
{
PlexLog.warn("Failed to load player by UUID {0}: {1}", uuid, e.getMessage());
return null; return null;
} }
} }
@@ -83,12 +79,12 @@ public class SQLPlayerData implements PlayerRepository
{ {
try try
{ {
PlayerEntity entity = players.queryForId(uuid.toString()); return jdbi.withHandle(h -> h.createQuery("SELECT last_known_name FROM players WHERE uuid = :u")
return entity == null ? null : entity.getLastKnownName(); .bind("u", uuid.toString()).mapTo(String.class).findFirst().orElse(null));
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to load player name by UUID {0}: {1}", uuid, e.getMessage());
return null; return null;
} }
} }
@@ -102,11 +98,16 @@ public class SQLPlayerData implements PlayerRepository
{ {
try try
{ {
return toPlayer(players.queryBuilder().limit(1L).where().eq("last_known_name", username).queryForFirst(), loadExtraData); return jdbi.withHandle(h ->
}
catch (SQLException e)
{ {
e.printStackTrace(); PlayerEntity e = h.createQuery("SELECT * FROM players WHERE last_known_name = :n LIMIT 1")
.bind("n", username).map((rs, ctx) -> mapRow(rs)).findFirst().orElse(null);
return toPlayer(h, e, loadExtraData);
});
}
catch (JdbiException e)
{
PlexLog.warn("Failed to load player by name {0}: {1}", username, e.getMessage());
return null; return null;
} }
} }
@@ -120,39 +121,46 @@ public class SQLPlayerData implements PlayerRepository
{ {
try try
{ {
PlayerIpEntity playerIp = playerIps.queryBuilder().limit(1L).where().eq("ip", ip).queryForFirst(); return jdbi.withHandle(h ->
if (playerIp != null)
{ {
return toPlayer(players.queryForId(playerIp.getPlayerUuid()), true); String uuid = h.createQuery("SELECT player_uuid FROM player_ips WHERE ip = :ip LIMIT 1")
} .bind("ip", ip).mapTo(String.class).findFirst().orElse(null);
if (uuid == null)
for (PlayerEntity entity : players.queryForAll())
{ {
List<String> ips = parseIps(entity.getIps());
if (ips.contains(ip))
{
syncIps(entity.getUuid(), ips);
return toPlayer(entity, true);
}
}
}
catch (SQLException e)
{
e.printStackTrace();
}
return null; return null;
} }
PlayerEntity e = h.createQuery("SELECT * FROM players WHERE uuid = :u")
.bind("u", uuid).map((rs, ctx) -> mapRow(rs)).findFirst().orElse(null);
return toPlayer(h, e, true);
});
}
catch (JdbiException e)
{
PlexLog.warn("Failed to load player by IP {0}: {1}", ip, e.getMessage());
return null;
}
}
public void update(PlexPlayer player) public void update(PlexPlayer player)
{ {
try try
{ {
players.createOrUpdate(toEntity(player)); jdbi.useTransaction(h ->
syncIps(player.getUuid().toString(), player.getIps());
}
catch (SQLException e)
{ {
e.printStackTrace(); h.createUpdate(storageType.playerUpsertSql())
.bind("uuid", player.getUuid().toString())
.bind("name", player.getName())
.bind("login", player.getLoginMessage())
.bind("prefix", player.getPrefix())
.bind("staffChat", player.isStaffChat())
.bind("commandSpy", player.isCommandSpy())
.execute();
syncIps(h, player.getUuid().toString(), player.getIps());
});
}
catch (JdbiException e)
{
PlexLog.warn("Failed to update player {0}: {1}", player.getUuid(), e.getMessage());
} }
} }
@@ -161,7 +169,35 @@ public class SQLPlayerData implements PlayerRepository
update(player); update(player);
} }
private PlexPlayer toPlayer(PlayerEntity entity, boolean loadExtraData) private static PlayerEntity mapRow(java.sql.ResultSet rs) throws java.sql.SQLException
{
PlayerEntity e = new PlayerEntity();
e.setUuid(rs.getString("uuid"));
e.setLastKnownName(rs.getString("last_known_name"));
e.setLoginMessage(rs.getString("login_msg"));
e.setPrefix(rs.getString("prefix"));
e.setStaffChat(rs.getBoolean("staffChat"));
e.setCommandSpy(rs.getBoolean("commandspy"));
return e;
}
private List<String> loadIps(Handle h, String uuid)
{
return h.createQuery("SELECT ip FROM player_ips WHERE player_uuid = :u")
.bind("u", uuid).mapTo(String.class).list();
}
private void syncIps(Handle h, String playerUuid, List<String> ips)
{
h.createUpdate("DELETE FROM player_ips WHERE player_uuid = :u").bind("u", playerUuid).execute();
for (String ip : ips.stream().distinct().toList())
{
h.createUpdate("INSERT INTO player_ips (player_uuid, ip) VALUES (:u, :ip)")
.bind("u", playerUuid).bind("ip", ip).execute();
}
}
private PlexPlayer toPlayer(Handle h, PlayerEntity entity, boolean loadExtraData)
{ {
if (entity == null) if (entity == null)
{ {
@@ -173,7 +209,7 @@ public class SQLPlayerData implements PlayerRepository
plexPlayer.setLoginMessage(entity.getLoginMessage()); plexPlayer.setLoginMessage(entity.getLoginMessage());
plexPlayer.setPrefix(entity.getPrefix()); plexPlayer.setPrefix(entity.getPrefix());
plexPlayer.setStaffChat(entity.isStaffChat()); plexPlayer.setStaffChat(entity.isStaffChat());
plexPlayer.setIps(parseIps(entity.getIps())); plexPlayer.setIps(loadIps(h, entity.getUuid()));
plexPlayer.setCommandSpy(entity.isCommandSpy()); plexPlayer.setCommandSpy(entity.isCommandSpy());
if (loadExtraData) if (loadExtraData)
{ {
@@ -182,41 +218,4 @@ public class SQLPlayerData implements PlayerRepository
} }
return plexPlayer; return plexPlayer;
} }
private PlayerEntity toEntity(PlexPlayer player)
{
PlayerEntity entity = new PlayerEntity();
entity.setUuid(player.getUuid().toString());
entity.setLastKnownName(player.getName());
entity.setLoginMessage(player.getLoginMessage());
entity.setPrefix(player.getPrefix());
entity.setStaffChat(player.isStaffChat());
entity.setIps(GSON.toJson(player.getIps()));
entity.setCommandSpy(player.isCommandSpy());
return entity;
}
private List<String> parseIps(String ips)
{
if (ips == null || ips.isBlank())
{
return List.of();
}
List<String> parsed = GSON.fromJson(ips, new TypeToken<List<String>>()
{
}.getType());
return parsed == null ? List.of() : parsed;
}
private void syncIps(String playerUuid, List<String> ips) throws SQLException
{
DeleteBuilder<PlayerIpEntity, Long> delete = playerIps.deleteBuilder();
delete.where().eq("player_uuid", playerUuid);
delete.delete();
for (String ip : ips.stream().distinct().toList())
{
playerIps.create(new PlayerIpEntity(playerUuid, ip));
}
}
} }
@@ -3,48 +3,41 @@ package dev.plex.storage.player;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import dev.plex.storage.SQLConnection;
import dev.plex.storage.StorageType; import dev.plex.storage.StorageType;
import dev.plex.util.PlexLog;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.JdbiException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public class SQLPlayerModuleData implements PlayerModuleDataRepository public class SQLPlayerModuleData implements PlayerModuleDataRepository
{ {
private final SQLConnection sqlConnection; private final Jdbi jdbi;
private final StorageType storageType; private final StorageType storageType;
public SQLPlayerModuleData(SQLConnection sqlConnection, StorageType storageType) public SQLPlayerModuleData(Jdbi jdbi, StorageType storageType)
{ {
this.sqlConnection = sqlConnection; this.jdbi = jdbi;
this.storageType = storageType; this.storageType = storageType;
} }
@Override @Override
public Optional<JsonElement> get(UUID playerUuid, String module, String key) public Optional<JsonElement> get(UUID playerUuid, String module, String key)
{ {
String sql = "SELECT value_json FROM player_module_data WHERE player_uuid = ? AND module = ? AND data_key = ?"; try
try (Connection connection = sqlConnection.getConnection(); PreparedStatement statement = connection.prepareStatement(sql))
{ {
statement.setString(1, playerUuid.toString()); return jdbi.withHandle(h -> h.createQuery(
statement.setString(2, module); "SELECT value_json FROM player_module_data WHERE player_uuid = :p AND module = :m AND data_key = :k")
statement.setString(3, key); .bind("p", playerUuid.toString())
try (ResultSet resultSet = statement.executeQuery()) .bind("m", module)
{ .bind("k", key)
if (!resultSet.next()) .mapTo(String.class).findFirst())
{ .map(JsonParser::parseString);
return Optional.empty();
} }
return Optional.of(JsonParser.parseString(resultSet.getString("value_json"))); catch (JdbiException | JsonSyntaxException e)
}
}
catch (SQLException | JsonSyntaxException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to load player module data {0}/{1}/{2}: {3}", playerUuid, module, key, e.getMessage());
return Optional.empty(); return Optional.empty();
} }
} }
@@ -52,35 +45,37 @@ public class SQLPlayerModuleData implements PlayerModuleDataRepository
@Override @Override
public void set(UUID playerUuid, String module, String key, JsonElement value) public void set(UUID playerUuid, String module, String key, JsonElement value)
{ {
try (Connection connection = sqlConnection.getConnection(); PreparedStatement statement = connection.prepareStatement(storageType.playerModuleDataUpsertSql())) try
{ {
statement.setString(1, playerUuid.toString()); jdbi.useHandle(h -> h.createUpdate(storageType.playerModuleDataUpsertSql())
statement.setString(2, module); .bind(0, playerUuid.toString())
statement.setString(3, key); .bind(1, module)
statement.setString(4, value.toString()); .bind(2, key)
statement.setLong(5, System.currentTimeMillis()); .bind(3, value.toString())
statement.executeUpdate(); .bind(4, System.currentTimeMillis())
.execute());
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to save player module data {0}/{1}/{2}: {3}", playerUuid, module, key, e.getMessage());
} }
} }
@Override @Override
public void remove(UUID playerUuid, String module, String key) public void remove(UUID playerUuid, String module, String key)
{ {
String sql = "DELETE FROM player_module_data WHERE player_uuid = ? AND module = ? AND data_key = ?"; try
try (Connection connection = sqlConnection.getConnection(); PreparedStatement statement = connection.prepareStatement(sql))
{ {
statement.setString(1, playerUuid.toString()); jdbi.useHandle(h -> h.createUpdate(
statement.setString(2, module); "DELETE FROM player_module_data WHERE player_uuid = :p AND module = :m AND data_key = :k")
statement.setString(3, key); .bind("p", playerUuid.toString())
statement.executeUpdate(); .bind("m", module)
.bind("k", key)
.execute());
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to remove player module data {0}/{1}/{2}: {3}", playerUuid, module, key, e.getMessage());
} }
} }
} }
@@ -1,16 +1,14 @@
package dev.plex.storage.punishment; package dev.plex.storage.punishment;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.stmt.DeleteBuilder;
import dev.plex.punishment.extra.Note; import dev.plex.punishment.extra.Note;
import dev.plex.storage.database.entity.NoteEntity; import dev.plex.storage.database.entity.NoteEntity;
import dev.plex.storage.repository.NoteRepository; import dev.plex.storage.repository.NoteRepository;
import dev.plex.util.PlexLog;
import dev.plex.util.TimeUtils; import dev.plex.util.TimeUtils;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.JdbiException;
import java.sql.SQLException;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@@ -23,21 +21,14 @@ import java.util.concurrent.Executor;
public class SQLNotes implements NoteRepository public class SQLNotes implements NoteRepository
{ {
private final Dao<NoteEntity, Long> notes; private final Jdbi jdbi;
private final Executor executor; private final Executor executor;
public SQLNotes(ConnectionSource connectionSource, Executor executor) public SQLNotes(Jdbi jdbi, Executor executor)
{ {
try this.jdbi = jdbi;
{
this.notes = DaoManager.createDao(connectionSource, NoteEntity.class);
this.executor = executor; this.executor = executor;
} }
catch (SQLException e)
{
throw new IllegalStateException("Failed to create note DAO", e);
}
}
public CompletableFuture<List<Note>> getNotes(UUID uuid) public CompletableFuture<List<Note>> getNotes(UUID uuid)
{ {
@@ -45,15 +36,16 @@ public class SQLNotes implements NoteRepository
{ {
try try
{ {
return notes.queryForEq("uuid", uuid.toString()).stream() return jdbi.withHandle(h -> h.createQuery("SELECT * FROM notes WHERE uuid = :u")
.bind("u", uuid.toString()).map((rs, ctx) -> mapRow(rs)).list()).stream()
.sorted(Comparator.comparingInt(NoteEntity::getId)) .sorted(Comparator.comparingInt(NoteEntity::getId))
.map(this::toNote) .map(this::toNote)
.flatMap(Optional::stream) .flatMap(Optional::stream)
.toList(); .toList();
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to load notes for {0}: {1}", uuid, e.getMessage());
return Lists.newArrayList(); return Lists.newArrayList();
} }
}, executor); }, executor);
@@ -65,13 +57,14 @@ public class SQLNotes implements NoteRepository
{ {
try try
{ {
DeleteBuilder<NoteEntity, Long> delete = notes.deleteBuilder(); jdbi.useHandle(h -> h.createUpdate("DELETE FROM notes WHERE uuid = :u AND id = :id")
delete.where().eq("uuid", uuid.toString()).and().eq("id", id); .bind("u", uuid.toString())
delete.delete(); .bind("id", id)
.execute());
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to delete note {0} for {1}: {2}", id, uuid, e.getMessage());
} }
}, executor); }, executor);
} }
@@ -82,22 +75,40 @@ public class SQLNotes implements NoteRepository
{ {
try try
{ {
int nextId = notes.queryForEq("uuid", note.getUuid().toString()).stream() int nextId = jdbi.withHandle(h -> h.createQuery("SELECT COALESCE(MAX(id), 0) FROM notes WHERE uuid = :u")
.map(NoteEntity::getId) .bind("u", note.getUuid().toString()).mapTo(Integer.class).one()) + 1;
.max(Integer::compareTo)
.orElse(0) + 1;
NoteEntity entity = toEntity(note); NoteEntity entity = toEntity(note);
entity.setId(nextId); entity.setId(nextId);
notes.create(entity); jdbi.useHandle(h -> h.createUpdate(
"INSERT INTO notes (id, uuid, written_by_uuid, note, timestamp) " +
"VALUES (:id, :uuid, :writtenBy, :note, :ts)")
.bind("id", entity.getId())
.bind("uuid", entity.getUuid())
.bind("writtenBy", entity.getWrittenByUuid())
.bind("note", entity.getNote())
.bind("ts", entity.getTimestamp())
.execute());
note.setId(nextId); note.setId(nextId);
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to add note for {0}: {1}", note.getUuid(), e.getMessage());
} }
}, executor); }, executor);
} }
private static NoteEntity mapRow(java.sql.ResultSet rs) throws java.sql.SQLException
{
NoteEntity e = new NoteEntity();
e.setRowId(rs.getLong("row_id"));
e.setId(rs.getInt("id"));
e.setUuid(rs.getString("uuid"));
e.setWrittenByUuid(rs.getString("written_by_uuid"));
e.setNote(rs.getString("note"));
e.setTimestamp(rs.getLong("timestamp"));
return e;
}
private Optional<Note> toNote(NoteEntity entity) private Optional<Note> toNote(NoteEntity entity)
{ {
try try
@@ -1,10 +1,6 @@
package dev.plex.storage.punishment; package dev.plex.storage.punishment;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.stmt.UpdateBuilder;
import dev.plex.api.punishment.PunishmentSource; import dev.plex.api.punishment.PunishmentSource;
import dev.plex.punishment.Punishment; import dev.plex.punishment.Punishment;
import dev.plex.punishment.PunishmentType; import dev.plex.punishment.PunishmentType;
@@ -12,8 +8,9 @@ import dev.plex.storage.database.entity.PunishmentEntity;
import dev.plex.storage.repository.PunishmentRepository; import dev.plex.storage.repository.PunishmentRepository;
import dev.plex.util.PlexLog; import dev.plex.util.PlexLog;
import dev.plex.util.TimeUtils; import dev.plex.util.TimeUtils;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.JdbiException;
import java.sql.SQLException;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@@ -24,21 +21,14 @@ import java.util.concurrent.Executor;
public class SQLPunishment implements PunishmentRepository public class SQLPunishment implements PunishmentRepository
{ {
private final Dao<PunishmentEntity, Long> punishments; private final Jdbi jdbi;
private final Executor executor; private final Executor executor;
public SQLPunishment(ConnectionSource connectionSource, Executor executor) public SQLPunishment(Jdbi jdbi, Executor executor)
{ {
try this.jdbi = jdbi;
{
this.punishments = DaoManager.createDao(connectionSource, PunishmentEntity.class);
this.executor = executor; this.executor = executor;
} }
catch (SQLException e)
{
throw new IllegalStateException("Failed to create punishment DAO", e);
}
}
public CompletableFuture<List<Punishment>> getPunishments() public CompletableFuture<List<Punishment>> getPunishments()
{ {
@@ -46,11 +36,12 @@ public class SQLPunishment implements PunishmentRepository
{ {
try try
{ {
return punishments.queryForAll().stream().map(this::toPunishment).toList(); return jdbi.withHandle(h -> h.createQuery("SELECT * FROM punishments")
.map((rs, ctx) -> mapRow(rs)).list()).stream().map(this::toPunishment).toList();
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to load punishments: {0}", e.getMessage());
return Lists.newArrayList(); return Lists.newArrayList();
} }
}, executor); }, executor);
@@ -60,11 +51,13 @@ public class SQLPunishment implements PunishmentRepository
{ {
try try
{ {
return punishments.queryForEq("punished_uuid", uuid.toString()).stream().map(this::toPunishment).toList(); return jdbi.withHandle(h -> h.createQuery("SELECT * FROM punishments WHERE punished_uuid = :u")
.bind("u", uuid.toString()).map((rs, ctx) -> mapRow(rs)).list())
.stream().map(this::toPunishment).toList();
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to load punishments for {0}: {1}", uuid, e.getMessage());
return Lists.newArrayList(); return Lists.newArrayList();
} }
} }
@@ -73,11 +66,13 @@ public class SQLPunishment implements PunishmentRepository
{ {
try try
{ {
return punishments.queryForEq("ip", ip).stream().map(this::toPunishment).toList(); return jdbi.withHandle(h -> h.createQuery("SELECT * FROM punishments WHERE ip = :ip")
.bind("ip", ip).map((rs, ctx) -> mapRow(rs)).list())
.stream().map(this::toPunishment).toList();
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to load punishments for IP {0}: {1}", ip, e.getMessage());
return Lists.newArrayList(); return Lists.newArrayList();
} }
} }
@@ -89,11 +84,26 @@ public class SQLPunishment implements PunishmentRepository
try try
{ {
PlexLog.debug("Persisting punishment for " + punishment.getPunished()); PlexLog.debug("Persisting punishment for " + punishment.getPunished());
punishments.create(toEntity(punishment)); PunishmentEntity e = toEntity(punishment);
jdbi.useHandle(h -> h.createUpdate(
"INSERT INTO punishments (punished_uuid, punisher_uuid, source, punisher_reference, ip, type, reason, customTime, active, issueDate, endDate) " +
"VALUES (:punishedUuid, :punisherUuid, :source, :punisherReference, :ip, :type, :reason, :customTime, :active, :issueDate, :endDate)")
.bind("punishedUuid", e.getPunishedUuid())
.bind("punisherUuid", e.getPunisherUuid())
.bind("source", e.getSource())
.bind("punisherReference", e.getPunisherReference())
.bind("ip", e.getIp())
.bind("type", e.getType())
.bind("reason", e.getReason())
.bind("customTime", e.isCustomTime())
.bind("active", e.isActive())
.bind("issueDate", e.getIssueDate())
.bind("endDate", e.getEndDate())
.execute());
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to persist punishment for {0}: {1}", punishment.getPunished(), e.getMessage());
} }
}, executor); }, executor);
} }
@@ -118,17 +128,37 @@ public class SQLPunishment implements PunishmentRepository
{ {
try try
{ {
UpdateBuilder<PunishmentEntity, Long> update = punishments.updateBuilder(); jdbi.useHandle(h -> h.createUpdate(
update.updateColumnValue("active", active); "UPDATE punishments SET active = :active WHERE punished_uuid = :u AND type = :t")
update.where().eq("punished_uuid", punished.toString()).and().eq("type", type.name()); .bind("active", active)
update.update(); .bind("u", punished.toString())
.bind("t", type.name())
.execute());
} }
catch (SQLException e) catch (JdbiException e)
{ {
e.printStackTrace(); PlexLog.warn("Failed to update punishment state for {0}: {1}", punished, e.getMessage());
} }
} }
private static PunishmentEntity mapRow(java.sql.ResultSet rs) throws java.sql.SQLException
{
PunishmentEntity e = new PunishmentEntity();
e.setId(rs.getLong("id"));
e.setPunishedUuid(rs.getString("punished_uuid"));
e.setPunisherUuid(rs.getString("punisher_uuid"));
e.setSource(rs.getString("source"));
e.setPunisherReference(rs.getString("punisher_reference"));
e.setIp(rs.getString("ip"));
e.setType(rs.getString("type"));
e.setReason(rs.getString("reason"));
e.setCustomTime(rs.getBoolean("customTime"));
e.setActive(rs.getBoolean("active"));
e.setIssueDate(rs.getLong("issueDate"));
e.setEndDate(rs.getLong("endDate"));
return e;
}
private Punishment toPunishment(PunishmentEntity entity) private Punishment toPunishment(PunishmentEntity entity)
{ {
UUID punisher = entity.getPunisherUuid() == null || entity.getPunisherUuid().isBlank() ? null : UUID.fromString(entity.getPunisherUuid()); UUID punisher = entity.getPunisherUuid() == null || entity.getPunisherUuid().isBlank() ? null : UUID.fromString(entity.getPunisherUuid());
@@ -88,9 +88,9 @@ public class PlexUtils
public static void testConnections(Plex plugin) public static void testConnections(Plex plugin)
{ {
if (plugin.getSqlConnection().getDataSource() != null) if (plugin.getDatabase().getDataSource() != null)
{ {
try (Connection ignored = plugin.getSqlConnection().getCon()) try (Connection ignored = plugin.getDatabase().getConnection())
{ {
PlexLog.log("Successfully enabled " + plugin.getStorageType().getDisplayName() + "!"); PlexLog.log("Successfully enabled " + plugin.getStorageType().getDisplayName() + "!");
} }
@@ -4,7 +4,6 @@ CREATE TABLE IF NOT EXISTS `players` (
`login_msg` VARCHAR(2000), `login_msg` VARCHAR(2000),
`prefix` VARCHAR(2000), `prefix` VARCHAR(2000),
`staffChat` BOOLEAN, `staffChat` BOOLEAN,
`ips` VARCHAR(2000),
`commandspy` BOOLEAN, `commandspy` BOOLEAN,
PRIMARY KEY (`uuid`), PRIMARY KEY (`uuid`),
INDEX `idx_players_last_known_name` (`last_known_name`) INDEX `idx_players_last_known_name` (`last_known_name`)
@@ -4,7 +4,6 @@ CREATE TABLE IF NOT EXISTS players (
login_msg VARCHAR(2000), login_msg VARCHAR(2000),
prefix VARCHAR(2000), prefix VARCHAR(2000),
staffChat BOOLEAN, staffChat BOOLEAN,
ips VARCHAR(2000),
commandspy BOOLEAN commandspy BOOLEAN
); );
@@ -4,7 +4,6 @@ CREATE TABLE IF NOT EXISTS players (
login_msg VARCHAR(2000), login_msg VARCHAR(2000),
prefix VARCHAR(2000), prefix VARCHAR(2000),
staffChat BOOLEAN, staffChat BOOLEAN,
ips VARCHAR(2000),
commandspy BOOLEAN commandspy BOOLEAN
); );