Fully implement new SQL schema & add migration code

This commit is contained in:
Luck 2017-01-08 19:33:27 +00:00
parent 773bfe1407
commit 960c2291b6
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
13 changed files with 338 additions and 696 deletions

View File

@ -173,6 +173,10 @@ data:
password: ''
pool-size: 10 # The size of the MySQL connection pool.
# The prefix for all LuckPerms tables. Change this is you want to use different tables for different servers.
# This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with this database.
table_prefix: 'luckperms_'
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes: 3

View File

@ -115,6 +115,10 @@ data:
password: ''
pool-size: 10 # The size of the MySQL connection pool.
# The prefix for all LuckPerms tables. Change this is you want to use different tables for different servers.
# This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with this database.
table_prefix: 'luckperms_'
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes: 3

View File

@ -79,6 +79,7 @@ public abstract class AbstractConfiguration<T extends LuckPermsPlugin> implement
private Map<String, String> groupNameRewrites;
private List<Rule> defaultAssignments;
private DatastoreConfiguration databaseValues;
private String sqlTablePrefix;
private String storageMethod;
private boolean splitStorage;
private Map<String, String> splitStorageOptions;
@ -163,6 +164,7 @@ public abstract class AbstractConfiguration<T extends LuckPermsPlugin> implement
getString("data.password", null),
getInt("data.pool-size", 10)
);
sqlTablePrefix = getString("data.table_prefix", "luckperms_");
storageMethod = getString("storage-method", defaultStorage);
splitStorage = getBoolean("split-storage.enabled", false);
splitStorageOptions = ImmutableMap.<String, String>builder()

View File

@ -98,6 +98,8 @@ public interface LPConfiguration {
DatastoreConfiguration getDatabaseValues();
String getSqlTablePrefix();
String getStorageMethod();
boolean isSplitStorage();

View File

@ -30,10 +30,11 @@ import me.lucko.luckperms.common.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.backing.AbstractBacking;
import me.lucko.luckperms.common.storage.backing.JSONBacking;
import me.lucko.luckperms.common.storage.backing.MongoDBBacking;
import me.lucko.luckperms.common.storage.backing.SQLLegacyBacking;
import me.lucko.luckperms.common.storage.backing.SQLBacking;
import me.lucko.luckperms.common.storage.backing.YAMLBacking;
import me.lucko.luckperms.common.storage.backing.sqlprovider.H2Provider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.MySQLProvider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.PostgreSQLProvider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.SQLiteProvider;
import me.lucko.luckperms.common.utils.ImmutableCollectors;
@ -120,11 +121,13 @@ public class StorageFactory {
private static AbstractBacking makeBacking(StorageType method, LuckPermsPlugin plugin) {
switch (method) {
case MYSQL:
return new SQLLegacyBacking(plugin, new MySQLProvider(plugin.getConfiguration().getDatabaseValues()));
return new SQLBacking(plugin, new MySQLProvider(plugin.getConfiguration().getDatabaseValues()), plugin.getConfiguration().getSqlTablePrefix());
case SQLITE:
return new SQLLegacyBacking(plugin, new SQLiteProvider(new File(plugin.getDataFolder(), "luckperms.sqlite")));
return new SQLBacking(plugin, new SQLiteProvider(new File(plugin.getDataFolder(), "luckperms.sqlite")), plugin.getConfiguration().getSqlTablePrefix());
case H2:
return new SQLLegacyBacking(plugin, new H2Provider(new File(plugin.getDataFolder(), "luckperms.db")));
return new SQLBacking(plugin, new H2Provider(new File(plugin.getDataFolder(), "luckperms.db")), plugin.getConfiguration().getSqlTablePrefix());
case POSTGRESQL:
return new SQLBacking(plugin, new PostgreSQLProvider(plugin.getConfiguration().getDatabaseValues()), plugin.getConfiguration().getSqlTablePrefix());
case MONGODB:
return new MongoDBBacking(plugin, plugin.getConfiguration().getDatabaseValues());
case YAML:

View File

@ -130,7 +130,7 @@ public class JSONBacking extends FlatfileBacking {
if (user.getName() == null || user.getName().equalsIgnoreCase("null")) {
user.setName(name1);
} else {
if (!name1.equals(user.getName())) {
if (!name1.equalsIgnoreCase(user.getName())) {
save = true;
}
}

View File

@ -235,7 +235,7 @@ public class MongoDBBacking extends AbstractBacking {
if (user.getName() == null || user.getName().equalsIgnoreCase("null")) {
user.setName(d.getString("name"));
} else {
if (!d.getString("name").equals(user.getName())) {
if (!d.getString("name").equalsIgnoreCase(user.getName())) {
save = true;
}
}

View File

@ -22,6 +22,8 @@
package me.lucko.luckperms.common.storage.backing;
import lombok.Getter;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
@ -38,6 +40,7 @@ import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.impl.GenericUserManager;
import me.lucko.luckperms.common.storage.backing.sqlprovider.SQLProvider;
import me.lucko.luckperms.common.storage.backing.utils.LegacySchemaMigration;
import me.lucko.luckperms.common.storage.backing.utils.NodeDataHolder;
import java.io.BufferedReader;
@ -65,7 +68,7 @@ public class SQLBacking extends AbstractBacking {
private static final Type LIST_STRING_TYPE = new TypeToken<List<String>>(){}.getType();
private static final String USER_PERMISSIONS_SELECT = "SELECT permission, value, server, world, expiry, contexts FROM {prefix}user_permissions WHERE uuid=?";
private static final String USER_PERMISSIONS_DELETE_SPECIFIC = "DELETE FROM {prefix}user_permissions WHERE uuid=?, permission=?, value=?, server=?, world=?, expiry=?, contexts=?";
private static final String USER_PERMISSIONS_DELETE_SPECIFIC = "DELETE FROM {prefix}user_permissions WHERE uuid=? AND permission=? AND value=? AND server=? AND world=? AND expiry=? AND contexts=?";
private static final String USER_PERMISSIONS_DELETE = "DELETE FROM {prefix}user_permissions WHERE uuid=?";
private static final String USER_PERMISSIONS_INSERT = "INSERT INTO {prefix}user_permissions(uuid, permission, value, server, world, expiry, contexts) VALUES(?, ?, ?, ?, ?, ?, ?)";
private static final String USER_PERMISSIONS_SELECT_DISTINCT = "SELECT DISTINCT uuid FROM {prefix}user_permissions";
@ -76,10 +79,11 @@ public class SQLBacking extends AbstractBacking {
private static final String PLAYER_INSERT = "INSERT INTO {prefix}players VALUES(?, ?, ?)";
private static final String PLAYER_UPDATE = "UPDATE {prefix}players SET username=? WHERE uuid=?";
private static final String PLAYER_UPDATE_FULL = "UPDATE {prefix}players SET username=?, primary_group=? WHERE uuid=?";
private static final String PLAYER_UPDATE_PRIMARY_GROUP = "UPDATE {prefix}players SET primary_group=? WHERE uuid=?";
private static final String GROUP_PERMISSIONS_SELECT = "SELECT permission, value, server, world, expiry, contexts FROM {prefix}group_permissions WHERE name=?";
private static final String GROUP_PERMISSIONS_DELETE = "DELETE FROM {prefix}group_permissions WHERE name=?";
private static final String GROUP_PERMISSIONS_DELETE_SPECIFIC = "DELETE FROM {prefix}group_permissions WHERE name=?, permission=?, value=?, server=?, world=?, expiry=?, contexts=?";
private static final String GROUP_PERMISSIONS_DELETE_SPECIFIC = "DELETE FROM {prefix}group_permissions WHERE name=? AND permission=? AND value=? AND server=? AND world=? AND expiry=? AND contexts=?";
private static final String GROUP_PERMISSIONS_INSERT = "INSERT INTO {prefix}group_permissions(name, permission, value, server, world, expiry, contexts) VALUES(?, ?, ?, ?, ?, ?, ?)";
private static final String GROUP_SELECT_ALL = "SELECT name FROM {prefix}groups";
@ -96,8 +100,12 @@ public class SQLBacking extends AbstractBacking {
private static final String ACTION_SELECT_ALL = "SELECT * FROM {prefix}actions";
@Getter
private final Gson gson;
@Getter
private final SQLProvider provider;
@Getter
private final Function<String, String> prefix;
public SQLBacking(LuckPermsPlugin plugin, SQLProvider provider, String prefix) {
@ -109,7 +117,14 @@ public class SQLBacking extends AbstractBacking {
private boolean tableExists(String table) throws SQLException {
try (Connection connection = provider.getConnection()) {
return connection.getMetaData().getTables(null, null, table.toUpperCase(), null).next();
try (ResultSet rs = connection.getMetaData().getTables(null, null, "%", null)) {
while (rs.next()) {
if (rs.getString(3).equalsIgnoreCase(table)) {
return true;
}
}
return false;
}
}
}
@ -152,6 +167,15 @@ public class SQLBacking extends AbstractBacking {
}
}
}
// Try migration from legacy backing
if (tableExists("lp_users")) {
plugin.getLog().severe("===== Legacy Schema Migration =====");
plugin.getLog().severe("Starting migration from legacy schema. This could take a while....");
plugin.getLog().severe("Please do not stop your server while the migration takes place.");
new LegacySchemaMigration(this).run();
}
}
setAcceptingLogins(true);
@ -289,7 +313,7 @@ public class SQLBacking extends AbstractBacking {
user.setName(name);
} else {
// The name in storage is not the same as their actual name.
if (!name.equals(user.getName())) {
if (!name.equalsIgnoreCase(user.getName())) {
save = true;
}
}
@ -327,6 +351,11 @@ public class SQLBacking extends AbstractBacking {
ps.setString(1, user.getUuid().toString());
ps.execute();
}
try (PreparedStatement ps = c.prepareStatement(prefix.apply(PLAYER_UPDATE_PRIMARY_GROUP))) {
ps.setString(1, "default");
ps.setString(2, user.getUuid().toString());
ps.execute();
}
} catch (SQLException e) {
e.printStackTrace();
return false;

View File

@ -1,685 +0,0 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.backing;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.common.LuckPermsPlugin;
import me.lucko.luckperms.common.core.UserIdentifier;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.Track;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.data.Log;
import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.impl.GenericUserManager;
import me.lucko.luckperms.common.storage.backing.sqlprovider.H2Provider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.MySQLProvider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.SQLProvider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.SQLiteProvider;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import static me.lucko.luckperms.common.core.model.PermissionHolder.exportToLegacy;
import static me.lucko.luckperms.common.storage.backing.sqlprovider.SQLProvider.QueryPS;
import static me.lucko.luckperms.common.storage.backing.sqlprovider.SQLProvider.QueryRS;
public class SQLLegacyBacking extends AbstractBacking {
private static final Type NM_TYPE = new TypeToken<Map<String, Boolean>>() {}.getType();
private static final Type T_TYPE = new TypeToken<List<String>>() {}.getType();
private static final String MYSQL_CREATETABLE_UUID = "CREATE TABLE IF NOT EXISTS `lp_uuid` (`name` VARCHAR(16) NOT NULL, `uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS `lp_users` (`uuid` VARCHAR(36) NOT NULL, `name` VARCHAR(16) NOT NULL, `primary_group` VARCHAR(36) NOT NULL, `perms` TEXT NOT NULL, PRIMARY KEY (`uuid`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_GROUPS = "CREATE TABLE IF NOT EXISTS `lp_groups` (`name` VARCHAR(36) NOT NULL, `perms` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INT AUTO_INCREMENT NOT NULL, `time` BIGINT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_UUID = "CREATE TABLE IF NOT EXISTS `lp_uuid` (`name` VARCHAR(16) NOT NULL, `uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS `lp_users` (`uuid` VARCHAR(36) NOT NULL, `name` VARCHAR(16) NOT NULL, `primary_group` VARCHAR(36) NOT NULL, `perms` TEXT NOT NULL, PRIMARY KEY (`uuid`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_GROUPS = "CREATE TABLE IF NOT EXISTS `lp_groups` (`name` VARCHAR(36) NOT NULL, `perms` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INT AUTO_INCREMENT NOT NULL, `time` BIGINT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET=utf8;";
private static final String SQLITE_CREATETABLE_UUID = "CREATE TABLE IF NOT EXISTS `lp_uuid` (`name` VARCHAR(16) NOT NULL, `uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`name`));";
private static final String SQLITE_CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS `lp_users` (`uuid` VARCHAR(36) NOT NULL, `name` VARCHAR(16) NOT NULL, `primary_group` VARCHAR(36) NOT NULL, `perms` TEXT NOT NULL, PRIMARY KEY (`uuid`));";
private static final String SQLITE_CREATETABLE_GROUPS = "CREATE TABLE IF NOT EXISTS `lp_groups` (`name` VARCHAR(36) NOT NULL, `perms` TEXT NULL, PRIMARY KEY (`name`));";
private static final String SQLITE_CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`));";
private static final String SQLITE_CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INTEGER PRIMARY KEY NOT NULL, `time` BIG INT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL);";
private static final Map<Class<? extends SQLProvider>, String[]> INIT_QUERIES = ImmutableMap.<Class<? extends SQLProvider>, String[]>builder()
.put(MySQLProvider.class, new String[]{MYSQL_CREATETABLE_UUID, MYSQL_CREATETABLE_USERS, MYSQL_CREATETABLE_GROUPS, MYSQL_CREATETABLE_TRACKS, MYSQL_CREATETABLE_ACTION})
.put(H2Provider.class, new String[]{H2_CREATETABLE_UUID, H2_CREATETABLE_USERS, H2_CREATETABLE_GROUPS, H2_CREATETABLE_TRACKS, H2_CREATETABLE_ACTION})
.put(SQLiteProvider.class, new String[]{SQLITE_CREATETABLE_UUID, SQLITE_CREATETABLE_USERS, SQLITE_CREATETABLE_GROUPS, SQLITE_CREATETABLE_TRACKS, SQLITE_CREATETABLE_ACTION})
.build();
private static final String USER_INSERT = "INSERT INTO lp_users VALUES(?, ?, ?, ?)";
private static final String USER_SELECT = "SELECT * FROM lp_users WHERE uuid=?";
private static final String USER_SELECT_ALL = "SELECT uuid FROM lp_users";
private static final String USER_UPDATE = "UPDATE lp_users SET name=?, primary_group = ?, perms=? WHERE uuid=?";
private static final String USER_DELETE = "DELETE FROM lp_users WHERE uuid=?";
private static final String USER_DELETE_ALL = "DELETE FROM lp_users WHERE perms=?";
private static final String GROUP_INSERT = "INSERT INTO lp_groups VALUES(?, ?)";
private static final String GROUP_SELECT = "SELECT perms FROM lp_groups WHERE name=?";
private static final String GROUP_SELECT_ALL = "SELECT * FROM lp_groups";
private static final String GROUP_UPDATE = "UPDATE lp_groups SET perms=? WHERE name=?";
private static final String GROUP_DELETE = "DELETE FROM lp_groups WHERE name=?";
private static final String TRACK_INSERT = "INSERT INTO lp_tracks VALUES(?, ?)";
private static final String TRACK_SELECT = "SELECT groups FROM lp_tracks WHERE name=?";
private static final String TRACK_SELECT_ALL = "SELECT * FROM lp_tracks";
private static final String TRACK_UPDATE = "UPDATE lp_tracks SET groups=? WHERE name=?";
private static final String TRACK_DELETE = "DELETE FROM lp_tracks WHERE name=?";
private static final String UUIDCACHE_INSERT = "INSERT INTO lp_uuid VALUES(?, ?)";
private static final String UUIDCACHE_SELECT = "SELECT uuid FROM lp_uuid WHERE name=?";
private static final String UUIDCACHE_SELECT_NAME = "SELECT name FROM lp_uuid WHERE uuid=?";
private static final String UUIDCACHE_UPDATE = "UPDATE lp_uuid SET uuid=? WHERE name=?";
private static final String ACTION_INSERT = "INSERT INTO lp_actions(`time`, `actor_uuid`, `actor_name`, `type`, `acted_uuid`, `acted_name`, `action`) VALUES(?, ?, ?, ?, ?, ?, ?)";
private static final String ACTION_SELECT_ALL = "SELECT * FROM lp_actions";
private final Gson gson;
private final SQLProvider provider;
public SQLLegacyBacking(LuckPermsPlugin plugin, SQLProvider provider) {
super(plugin, provider.getName());
this.provider = provider;
gson = new Gson();
}
private boolean runQuery(String query, QueryPS queryPS) {
return provider.runQuery(query, queryPS);
}
private boolean runQuery(String query, QueryPS queryPS, QueryRS queryRS) {
return provider.runQuery(query, queryPS, queryRS);
}
private boolean runQuery(String query) {
return provider.runQuery(query);
}
private boolean runQuery(String query, QueryRS queryRS) {
return provider.runQuery(query, queryRS);
}
private boolean setupTables(String[] tableQueries) {
boolean success = true;
for (String q : tableQueries) {
if (!runQuery(q)) success = false;
}
return success && cleanupUsers();
}
@Override
public void init() {
try {
provider.init();
if (!setupTables(INIT_QUERIES.get(provider.getClass()))) {
plugin.getLog().severe("Error occurred whilst initialising the database.");
shutdown();
} else {
setAcceptingLogins(true);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void shutdown() {
try {
provider.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean logAction(LogEntry entry) {
return runQuery(ACTION_INSERT, preparedStatement -> {
preparedStatement.setLong(1, entry.getTimestamp());
preparedStatement.setString(2, entry.getActor().toString());
preparedStatement.setString(3, entry.getActorName());
preparedStatement.setString(4, Character.toString(entry.getType()));
preparedStatement.setString(5, entry.getActed() == null ? "null" : entry.getActed().toString());
preparedStatement.setString(6, entry.getActedName());
preparedStatement.setString(7, entry.getAction());
});
}
@Override
public Log getLog() {
final Log.Builder log = Log.builder();
boolean success = runQuery(ACTION_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
final String actedUuid = resultSet.getString("acted_uuid");
LogEntry e = new LogEntry(
resultSet.getLong("time"),
UUID.fromString(resultSet.getString("actor_uuid")),
resultSet.getString("actor_name"),
resultSet.getString("type").toCharArray()[0],
actedUuid.equals("null") ? null : UUID.fromString(actedUuid),
resultSet.getString("acted_name"),
resultSet.getString("action")
);
log.add(e);
}
return true;
});
return success ? log.build() : null;
}
@Override
public boolean loadUser(UUID uuid, String username) {
User user = plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username));
user.getIoLock().lock();
try {
// screw "effectively final"
final String[] perms = new String[1];
final String[] pg = new String[1];
final String[] name = new String[1];
final boolean[] exists = {false};
boolean s = runQuery(USER_SELECT,
preparedStatement -> preparedStatement.setString(1, user.getUuid().toString()),
resultSet -> {
if (resultSet.next()) {
// User exists.
exists[0] = true;
perms[0] = resultSet.getString("perms");
pg[0] = resultSet.getString("primary_group");
name[0] = resultSet.getString("name");
}
return true;
}
);
if (!s) {
return false;
}
if (exists[0]) {
// User exists, let's load.
Map<String, Boolean> nodes = gson.fromJson(perms[0], NM_TYPE);
user.setNodes(nodes);
user.setPrimaryGroup(pg[0]);
boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false);
if (user.getName() == null || user.getName().equalsIgnoreCase("null")) {
user.setName(name[0]);
} else {
if (!name[0].equals(user.getName())) {
save = true;
}
}
if (save) {
String json = gson.toJson(exportToLegacy(user.getNodes()));
runQuery(USER_UPDATE, preparedStatement -> {
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPrimaryGroup());
preparedStatement.setString(3, json);
preparedStatement.setString(4, user.getUuid().toString());
});
}
} else {
if (GenericUserManager.shouldSave(user)) {
user.clearNodes();
user.setPrimaryGroup(null);
plugin.getUserManager().giveDefaultIfNeeded(user, false);
}
}
return true;
} finally {
user.getIoLock().unlock();
user.getRefreshBuffer().requestDirectly();
}
}
@Override
public boolean saveUser(User user) {
if (!GenericUserManager.shouldSave(user)) {
user.getIoLock().lock();
try {
return runQuery(USER_DELETE, preparedStatement -> {
preparedStatement.setString(1, user.getUuid().toString());
});
} finally {
user.getIoLock().unlock();
}
// return true above ^^^^^
}
user.getIoLock().lock();
try {
final boolean[] exists = {false};
boolean success = runQuery(USER_SELECT,
preparedStatement -> preparedStatement.setString(1, user.getUuid().toString()),
resultSet -> {
if (resultSet.next()) {
exists[0] = true;
}
return true;
}
);
if (!success) {
return false;
}
final String s = gson.toJson(exportToLegacy(user.getNodes()));
if (exists[0]) {
// User exists, let's update.
return runQuery(USER_UPDATE, preparedStatement -> {
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPrimaryGroup());
preparedStatement.setString(3, s);
preparedStatement.setString(4, user.getUuid().toString());
});
} else {
// Doesn't already exist, let's insert.
return runQuery(USER_INSERT, preparedStatement -> {
preparedStatement.setString(1, user.getUuid().toString());
preparedStatement.setString(2, user.getName());
preparedStatement.setString(3, user.getPrimaryGroup());
preparedStatement.setString(4, s);
});
}
} finally {
user.getIoLock().unlock();
}
}
@Override
public boolean cleanupUsers() {
return runQuery(USER_DELETE_ALL, preparedStatement -> {
preparedStatement.setString(1, "{\"group.default\":true}");
});
}
@Override
public Set<UUID> getUniqueUsers() {
Set<UUID> uuids = new HashSet<>();
boolean success = runQuery(USER_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
String uuid = resultSet.getString("uuid");
uuids.add(UUID.fromString(uuid));
}
return true;
});
return success ? uuids : null;
}
@Override
public boolean createAndLoadGroup(String name) {
Group group = plugin.getGroupManager().getOrMake(name);
group.getIoLock().lock();
try {
final boolean[] exists = {false};
final String[] perms = new String[1];
boolean s = runQuery(GROUP_SELECT,
preparedStatement -> preparedStatement.setString(1, group.getName()),
resultSet -> {
if (resultSet.next()) {
exists[0] = true;
perms[0] = resultSet.getString("perms");
}
return true;
}
);
if (!s) {
return false;
}
if (exists[0]) {
// Group exists, let's load.
Map<String, Boolean> nodes = gson.fromJson(perms[0], NM_TYPE);
group.setNodes(nodes);
return true;
} else {
String json = gson.toJson(exportToLegacy(group.getNodes()));
return runQuery(GROUP_INSERT, preparedStatement -> {
preparedStatement.setString(1, group.getName());
preparedStatement.setString(2, json);
});
}
} finally {
group.getIoLock().unlock();
}
}
@Override
public boolean loadGroup(String name) {
Group group = plugin.getGroupManager().getOrMake(name);
group.getIoLock().lock();
try {
final String[] perms = new String[1];
boolean s = runQuery(GROUP_SELECT,
preparedStatement -> preparedStatement.setString(1, name),
resultSet -> {
if (resultSet.next()) {
perms[0] = resultSet.getString("perms");
return true;
}
return false;
}
);
if (!s) {
return false;
}
// Group exists, let's load.
Map<String, Boolean> nodes = gson.fromJson(perms[0], NM_TYPE);
group.setNodes(nodes);
return true;
} finally {
group.getIoLock().unlock();
}
}
@Override
public boolean loadAllGroups() {
List<String> groups = new ArrayList<>();
boolean b = runQuery(GROUP_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
String name = resultSet.getString("name");
groups.add(name);
}
return true;
});
if (!b) {
return false;
}
for (String g : groups) {
if (!loadGroup(g)) {
b = false;
}
}
if (b) {
GroupManager gm = plugin.getGroupManager();
gm.getAll().values().stream()
.filter(g -> !groups.contains(g.getName()))
.forEach(gm::unload);
}
return b;
}
@Override
public boolean saveGroup(Group group) {
group.getIoLock().lock();
try {
String json = gson.toJson(exportToLegacy(group.getNodes()));
return runQuery(GROUP_UPDATE, preparedStatement -> {
preparedStatement.setString(1, json);
preparedStatement.setString(2, group.getName());
});
} finally {
group.getIoLock().unlock();
}
}
@Override
public boolean deleteGroup(Group group) {
group.getIoLock().lock();
boolean success;
try {
success = runQuery(GROUP_DELETE, preparedStatement -> {
preparedStatement.setString(1, group.getName());
});
} finally {
group.getIoLock().unlock();
}
if (success) plugin.getGroupManager().unload(group);
return success;
}
@Override
public boolean createAndLoadTrack(String name) {
Track track = plugin.getTrackManager().getOrMake(name);
track.getIoLock().lock();
try {
final boolean[] exists = {false};
final String[] groups = new String[1];
boolean s = runQuery(TRACK_SELECT,
preparedStatement -> preparedStatement.setString(1, track.getName()),
resultSet -> {
if (resultSet.next()) {
exists[0] = true;
groups[0] = resultSet.getString("groups");
}
return true;
}
);
if (!s) {
return false;
}
if (exists[0]) {
// Track exists, let's load.
track.setGroups(gson.fromJson(groups[0], T_TYPE));
return true;
} else {
String json = gson.toJson(track.getGroups());
return runQuery(TRACK_INSERT, preparedStatement -> {
preparedStatement.setString(1, track.getName());
preparedStatement.setString(2, json);
});
}
} finally {
track.getIoLock().unlock();
}
}
@Override
public boolean loadTrack(String name) {
Track track = plugin.getTrackManager().getOrMake(name);
track.getIoLock().lock();
try {
final String[] groups = {null};
boolean s = runQuery(TRACK_SELECT,
preparedStatement -> preparedStatement.setString(1, name),
resultSet -> {
if (resultSet.next()) {
groups[0] = resultSet.getString("groups");
return true;
}
return false;
}
);
if (!s) {
return false;
}
track.setGroups(gson.fromJson(groups[0], T_TYPE));
return true;
} finally {
track.getIoLock().unlock();
}
}
@Override
public boolean loadAllTracks() {
List<String> tracks = new ArrayList<>();
boolean b = runQuery(TRACK_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
String name = resultSet.getString("name");
tracks.add(name);
}
return true;
});
if (!b) {
return false;
}
for (String t : tracks) {
if (!loadTrack(t)) {
b = false;
}
}
if (b) {
TrackManager tm = plugin.getTrackManager();
tm.getAll().values().stream()
.filter(t -> !tracks.contains(t.getName()))
.forEach(tm::unload);
}
return b;
}
@Override
public boolean saveTrack(Track track) {
track.getIoLock().lock();
try {
String s = gson.toJson(track.getGroups());
return runQuery(TRACK_UPDATE, preparedStatement -> {
preparedStatement.setString(1, s);
preparedStatement.setString(2, track.getName());
});
} finally {
track.getIoLock().unlock();
}
}
@Override
public boolean deleteTrack(Track track) {
track.getIoLock().lock();
boolean success;
try {
success = runQuery(TRACK_DELETE, preparedStatement -> {
preparedStatement.setString(1, track.getName());
});
} finally {
track.getIoLock().unlock();
}
if (success) plugin.getTrackManager().unload(track);
return success;
}
@Override
public boolean saveUUIDData(String username, UUID uuid) {
final String u = username.toLowerCase();
final boolean[] update = {false};
boolean s = runQuery(UUIDCACHE_SELECT,
preparedStatement -> preparedStatement.setString(1, u),
resultSet -> {
if (resultSet.next()) {
update[0] = true;
}
return true;
}
);
if (!s) {
return false;
}
if (update[0]) {
return runQuery(UUIDCACHE_UPDATE, preparedStatement -> {
preparedStatement.setString(1, uuid.toString());
preparedStatement.setString(2, u);
});
} else {
return runQuery(UUIDCACHE_INSERT, preparedStatement -> {
preparedStatement.setString(1, u);
preparedStatement.setString(2, uuid.toString());
});
}
}
@Override
public UUID getUUID(String username) {
final String u = username.toLowerCase();
final UUID[] uuid = {null};
boolean success = runQuery(UUIDCACHE_SELECT,
preparedStatement -> preparedStatement.setString(1, u),
resultSet -> {
if (resultSet.next()) {
uuid[0] = UUID.fromString(resultSet.getString("uuid"));
return true;
}
return false;
}
);
return success ? uuid[0] : null;
}
@Override
public String getName(UUID uuid) {
final String u = uuid.toString();
final String[] name = {null};
boolean success = runQuery(UUIDCACHE_SELECT_NAME,
preparedStatement -> preparedStatement.setString(1, u),
resultSet -> {
if (resultSet.next()) {
name[0] = resultSet.getString("name");
return true;
}
return false;
}
);
return success ? name[0] : null;
}
}

View File

@ -119,7 +119,7 @@ public class YAMLBacking extends FlatfileBacking {
if (user.getName() == null || user.getName().equalsIgnoreCase("null")) {
user.setName(name);
} else {
if (!name.equals(user.getName())) {
if (!name.equalsIgnoreCase(user.getName())) {
save = true;
}
}

View File

@ -0,0 +1,277 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.backing.utils;
import lombok.RequiredArgsConstructor;
import com.google.common.collect.Lists;
import com.google.gson.reflect.TypeToken;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.storage.backing.SQLBacking;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class LegacySchemaMigration implements Runnable {
private static final Type NODE_MAP_TYPE = new TypeToken<Map<String, Boolean>>() {}.getType();
private final SQLBacking backing;
@Override
public void run() {
backing.getPlugin().getLog().info("Collecting UUID data from the old tables.");
Map<UUID, String> uuidData = new HashMap<>();
try (Connection c = backing.getProvider().getConnection()) {
try (PreparedStatement ps = c.prepareStatement("SELECT uuid, name FROM lp_uuid")) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
try {
uuidData.put(UUID.fromString(rs.getString("uuid")), rs.getString("name"));
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
backing.getPlugin().getLog().info("Found " + uuidData.size() + " uuid data entries. Copying to new tables...");
List<Map.Entry<UUID, String>> uuidEntries = uuidData.entrySet().stream().collect(Collectors.toList());
List<List<Map.Entry<UUID, String>>> partitionedUuidEntries = Lists.partition(uuidEntries, 100);
for (List<Map.Entry<UUID, String>> l : partitionedUuidEntries) {
try (Connection c = backing.getProvider().getConnection()) {
try (PreparedStatement ps = c.prepareStatement(backing.getPrefix().apply("INSERT INTO {prefix}players VALUES(?, ?, ?)"))) {
for (Map.Entry<UUID, String> e : l) {
ps.setString(1, e.getKey().toString());
ps.setString(2, e.getValue().toLowerCase());
ps.setString(3, "default");
ps.addBatch();
}
ps.executeBatch();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
uuidData.clear();
uuidEntries.clear();
partitionedUuidEntries.clear();
backing.getPlugin().getLog().info("Migrated all uuid data.");
backing.getPlugin().getLog().info("Starting user data migration.");
Set<UUID> users = new HashSet<>();
try (Connection c = backing.getProvider().getConnection()) {
try (PreparedStatement ps = c.prepareStatement("SELECT uuid FROM lp_users")) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
try {
users.add(UUID.fromString(rs.getString("uuid")));
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
backing.getPlugin().getLog().info("Found " + users.size() + " user data entries. Copying to new tables...");
AtomicInteger userCounter = new AtomicInteger(0);
for (UUID uuid : users) {
String permsJson = null;
String primaryGroup = null;
try (Connection c = backing.getProvider().getConnection()) {
try (PreparedStatement ps = c.prepareStatement("SELECT primary_group, perms FROM lp_users WHERE uuid=?")) {
ps.setString(1, uuid.toString());
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
permsJson = rs.getString("perms");
primaryGroup = rs.getString("primary_group");
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
if (permsJson == null || primaryGroup == null) {
new Throwable().printStackTrace();
continue;
}
Map<String, Boolean> convertedPerms = backing.getGson().fromJson(permsJson, NODE_MAP_TYPE);
if (convertedPerms == null) {
new Throwable().printStackTrace();
continue;
}
Set<NodeDataHolder> nodes = convertedPerms.entrySet().stream()
.map(e -> NodeFactory.fromSerialisedNode(e.getKey(), e.getValue()))
.map(NodeDataHolder::fromNode)
.collect(Collectors.toSet());
try (Connection c = backing.getProvider().getConnection()) {
try (PreparedStatement ps = c.prepareStatement(backing.getPrefix().apply("INSERT INTO {prefix}user_permissions(uuid, permission, value, server, world, expiry, contexts) VALUES(?, ?, ?, ?, ?, ?, ?)"))) {
for (NodeDataHolder nd : nodes) {
ps.setString(1, uuid.toString());
ps.setString(2, nd.getPermission());
ps.setBoolean(3, nd.isValue());
ps.setString(4, nd.getServer());
ps.setString(5, nd.getWorld());
ps.setLong(6, nd.getExpiry());
ps.setString(7, nd.getContexts());
ps.addBatch();
}
ps.executeBatch();
}
} catch (SQLException e) {
e.printStackTrace();
}
if (!primaryGroup.equalsIgnoreCase("default")) {
try (Connection c = backing.getProvider().getConnection()) {
try (PreparedStatement ps = c.prepareStatement(backing.getPrefix().apply("UPDATE {prefix}players SET primary_group=? WHERE uuid=?"))) {
ps.setString(1, primaryGroup);
ps.setString(2, uuid.toString());
ps.execute();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
int i = userCounter.incrementAndGet();
if (i % 100 == 0) {
backing.getPlugin().getLog().info("Migrated " + i + " users so far...");
}
}
users.clear();
backing.getPlugin().getLog().info("Migrated all user data.");
backing.getPlugin().getLog().info("Starting group data migration.");
Map<String, String> groupData = new HashMap<>();
try (Connection c = backing.getProvider().getConnection()) {
try (PreparedStatement ps = c.prepareStatement("SELECT name, perms FROM lp_groups")) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
groupData.put(rs.getString("name"), rs.getString("perms"));
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
backing.getPlugin().getLog().info("Found " + groupData.size() + " group data entries. Copying to new tables...");
for (Map.Entry<String, String> e : groupData.entrySet()) {
String name = e.getKey();
String permsJson = e.getValue();
try (Connection c = backing.getProvider().getConnection()) {
try (PreparedStatement ps = c.prepareStatement(backing.getPrefix().apply("INSERT INTO {prefix}groups VALUES(?)"))) {
ps.setString(1, name);
ps.execute();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
Map<String, Boolean> convertedPerms = backing.getGson().fromJson(permsJson, NODE_MAP_TYPE);
if (convertedPerms == null) {
new Throwable().printStackTrace();
continue;
}
Set<NodeDataHolder> nodes = convertedPerms.entrySet().stream()
.map(ent -> NodeFactory.fromSerialisedNode(ent.getKey(), ent.getValue()))
.map(NodeDataHolder::fromNode)
.collect(Collectors.toSet());
try (Connection c = backing.getProvider().getConnection()) {
try (PreparedStatement ps = c.prepareStatement(backing.getPrefix().apply("INSERT INTO {prefix}group_permissions(name, permission, value, server, world, expiry, contexts) VALUES(?, ?, ?, ?, ?, ?, ?)"))) {
for (NodeDataHolder nd : nodes) {
ps.setString(1, name);
ps.setString(2, nd.getPermission());
ps.setBoolean(3, nd.isValue());
ps.setString(4, nd.getServer());
ps.setString(5, nd.getWorld());
ps.setLong(6, nd.getExpiry());
ps.setString(7, nd.getContexts());
ps.addBatch();
}
ps.executeBatch();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
groupData.clear();
backing.getPlugin().getLog().info("Migrated all group data.");
backing.getPlugin().getLog().info("Renaming action and track tables.");
try (Connection c = backing.getProvider().getConnection()) {
try (PreparedStatement ps = c.prepareStatement(backing.getPrefix().apply("DROP TABLE {prefix}actions"))) {
ps.execute();
}
try (PreparedStatement ps = c.prepareStatement(backing.getPrefix().apply("ALTER TABLE lp_actions RENAME TO {prefix}actions"))) {
ps.execute();
}
try (PreparedStatement ps = c.prepareStatement(backing.getPrefix().apply("DROP TABLE {prefix}tracks"))) {
ps.execute();
}
try (PreparedStatement ps = c.prepareStatement(backing.getPrefix().apply("ALTER TABLE lp_tracks RENAME TO {prefix}tracks"))) {
ps.execute();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
backing.getPlugin().getLog().info("Legacy schema migration complete.");
}
}

View File

@ -23,6 +23,7 @@
package me.lucko.luckperms.common.storage.backing.utils;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
@ -38,6 +39,7 @@ import java.util.Map;
@Getter
@ToString
@EqualsAndHashCode
@AllArgsConstructor(staticName = "of")
public class NodeDataHolder {
private static final Gson GSON = new Gson();

View File

@ -116,6 +116,10 @@ data {
password=""
pool-size=10 # The size of the MySQL connection pool.
# The prefix for all LuckPerms tables. Change this is you want to use different tables for different servers.
# This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with this database.
table_prefix="luckperms_"
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes=3