/**
* PermissionsEx
* Copyright (C) zml and PermissionsEx contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ninja.leaping.permissionsex.backend.sql;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import ninja.leaping.permissionsex.PermissionsEx;
import ninja.leaping.permissionsex.backend.ConversionUtils;
import ninja.leaping.permissionsex.backend.sql.dao.LegacyMigration;
import ninja.leaping.permissionsex.backend.sql.dao.SchemaMigration;
import ninja.leaping.permissionsex.util.GuavaCollectors;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Schema migrations for the SQL database
*/
public class SchemaMigrations {
public static final int VERSION_LATEST = 3;
public static List<SchemaMigration> getMigrations() {
List<SchemaMigration> migrations = new ArrayList<>();
migrations.add(0, SchemaMigrations.initialToZero());
migrations.add(1, SchemaMigrations.zeroToOne());
migrations.add(2, SchemaMigrations.oneToTwo());
migrations.add(VERSION_LATEST, SchemaMigrations.twoToThree());
return migrations;
}
// Pre-2.x only needs to support MySQL because tbh nobody uses SQLite
public static SchemaMigration twoToThree() {
// The big one
return dao -> {
dao.legacy().renameTable(dao, "permissions", "permissions_old");
dao.legacy().renameTable(dao, "permissions_entity", "permissions_entity_old");
dao.legacy().renameTable(dao, "permissions_inheritance", "permissions_inheritance_old");
dao.initializeTables();
// Transfer world inheritance
try (PreparedStatement stmt = dao.prepareStatement("SELECT id, child, parent FROM {}permissions_inheritance_old WHERE type=2 ORDER BY child, parent, id ASC")) {
ResultSet rs = stmt.executeQuery();
try (PreparedStatement insert = dao.prepareStatement(dao.getInsertContextInheritanceQuery())) {
insert.setString(1, "world");
insert.setString(3, "world");
while (rs.next()) {
insert.setString(2, rs.getString(2));
insert.setString(4, rs.getString(3));
insert.addBatch();
}
insert.executeBatch();
}
}
Map<String, List<SubjectRef>> defaultSubjects = new HashMap<>();
Map<String, List<Map.Entry<SubjectRef, Integer>>> tempRankLadders = new HashMap<>();
try (PreparedStatement select = dao.prepareStatement("SELECT type, name FROM {}permissions_entity_old")) {
ResultSet rs = select.executeQuery();
while (rs.next()) {
SubjectRef ref = dao.getOrCreateSubjectRef(LegacyMigration.Type.values()[rs.getInt(1)].name().toLowerCase(), rs.getString(2));
Segment currentSeg = null;
String currentWorld = null;
Map<String, Segment> worldSegments = new HashMap<>();
try (PreparedStatement selectPermissionsOptions = dao.prepareStatement("SELECT id, permission, world, value FROM {}permissions_old WHERE type=? AND name=? ORDER BY world, id DESC")) {
selectPermissionsOptions.setInt(1, rs.getInt(1));
selectPermissionsOptions.setString(2, rs.getString(2));
ResultSet perms = selectPermissionsOptions.executeQuery();
Map<String, Integer> newPerms = new HashMap<>();
Map<String, String> options = new HashMap<>();
String rank = null, rankLadder = null;
int defaultVal = 0;
while (perms.next()) {
String worldChecked = perms.getString(3);
if (worldChecked != null && worldChecked.isEmpty()) {
worldChecked = null;
}
if (currentSeg == null || !Objects.equals(worldChecked, currentWorld)) {
if (currentSeg != null) {
if (!options.isEmpty()) {
dao.setOptions(currentSeg, options);
options.clear();
}
if (!newPerms.isEmpty()) {
dao.setPermissions(currentSeg, newPerms);
newPerms.clear();
}
if (defaultVal != 0) {
dao.setDefaultValue(currentSeg, defaultVal);
defaultVal = 0;
}
}
currentWorld = worldChecked;
currentSeg = Segment.unallocated(currentWorld == null ? ImmutableSet.of() : ImmutableSet.of(Maps.immutableEntry("world", currentWorld)));
dao.allocateSegment(ref, currentSeg);
worldSegments.put(currentWorld, currentSeg);
}
String key = perms.getString(2);
final String value = perms.getString(4);
if (value == null || value.isEmpty()) {
// permission
int val = key.startsWith("-") ? -1 : 1;
if (val == -1) {
key = key.substring(1);
}
if (key.equals("*")) {
defaultVal = val;
continue;
}
key = ConversionUtils.convertLegacyPermission(key);
newPerms.put(key, val);
} else {
if (currentWorld == null) {
boolean rankEq = key.equals("rank"), rankLadderEq = !rankEq && key.equals("rank-ladder");
if (rankEq || rankLadderEq) {
if (rankEq) {
rank = value;
} else { // then it's the rank ladder
rankLadder = value;
}
if (rank != null && rankLadder != null) {
List<Map.Entry<SubjectRef, Integer>> ladder = tempRankLadders.computeIfAbsent(rankLadder, ign -> new ArrayList<>());
try {
ladder.add(Maps.immutableEntry(ref, Integer.parseInt(rank)));
} catch (IllegalArgumentException ex) {}
rankLadder = null;
rank = null;
}
continue;
}
}
if (key.equals("default") && value.equalsIgnoreCase("true")) {
defaultSubjects.computeIfAbsent(currentWorld, ign -> new ArrayList<>()).add(ref);
continue;
}
options.put(key, value);
}
}
if (currentSeg != null) {
if (!options.isEmpty()) {
dao.setOptions(currentSeg, options);
}
if (!newPerms.isEmpty()) {
dao.setPermissions(currentSeg, newPerms);
}
if (defaultVal != 0) {
dao.setDefaultValue(currentSeg, defaultVal);
}
if (rank != null) {
List<Map.Entry<SubjectRef, Integer>> ladder = tempRankLadders.computeIfAbsent("default", ign -> new ArrayList<>());
try {
ladder.add(Maps.immutableEntry(ref, Integer.parseInt(rank)));
} catch (IllegalArgumentException ex) {}
}
}
}
for (Map.Entry<String, List<Map.Entry<SubjectRef, Integer>>> ent : tempRankLadders.entrySet()) {
List<SubjectRef> ladder = ent.getValue().stream()
.sorted((a, b) -> Integer.compare(b.getValue(), a.getValue()))
.map(Map.Entry::getKey)
.collect(GuavaCollectors.toImmutableList());
dao.setRankLadder(ent.getKey(), new SqlRankLadder(ent.getKey(), ladder));
}
if (!defaultSubjects.isEmpty()) {
SubjectRef defaultSubj = dao.getOrCreateSubjectRef(PermissionsEx.SUBJECTS_DEFAULTS, PermissionsEx.SUBJECTS_USER);
List<Segment> segments = new ArrayList<>(dao.getSegments(defaultSubj));
for (Map.Entry<String, List<SubjectRef>> ent : defaultSubjects.entrySet()) {
Segment seg = null;
if (!segments.isEmpty()) {
for (Segment segment : segments) {
if (ent.getKey() == null && segment.getContexts().isEmpty()) {
seg = segment;
break;
} else if (segment.getContexts().size() == 1) {
Map.Entry<String, String> ctx = segment.getContexts().iterator().next();
if (ctx.getKey().equals("world") && ctx.getValue().equals(ent.getKey())) {
seg = segment;
break;
}
}
}
}
if (seg == null) {
seg = Segment.unallocated(ent.getKey() == null ? ImmutableSet.of() : ImmutableSet.of(Maps.immutableEntry("world", ent.getKey())));
dao.allocateSegment(defaultSubj, seg);
segments.add(seg);
}
dao.setParents(seg, ent.getValue());
}
}
try (PreparedStatement selectInheritance = dao.prepareStatement(dao.legacy().getSelectParentsQuery())) {
selectInheritance.setString(1, rs.getString(2));
selectInheritance.setInt(2, rs.getInt(1));
ResultSet inheritance = selectInheritance.executeQuery();
List<SubjectRef> newInheritance = new LinkedList<>();
while (inheritance.next()) {
if (currentSeg == null || !Objects.equals(inheritance.getString(3), currentWorld)) {
if (currentSeg != null && !newInheritance.isEmpty()) {
dao.setParents(currentSeg, newInheritance);
newInheritance.clear();
}
currentWorld = inheritance.getString(3);
currentSeg = worldSegments.get(currentWorld);
if (currentSeg == null) {
currentSeg = Segment.unallocated(currentWorld == null ? ImmutableSet.of() : ImmutableSet.of(Maps.immutableEntry("world", currentWorld)));
dao.allocateSegment(ref, currentSeg);
worldSegments.put(currentWorld, currentSeg);
}
}
newInheritance.add(dao.getOrCreateSubjectRef(PermissionsEx.SUBJECTS_GROUP, inheritance.getString(2)));
}
if (currentSeg != null && !newInheritance.isEmpty()) {
dao.setParents(currentSeg, newInheritance);
newInheritance.clear();
}
}
}
}
dao.deleteTable("permissions_old");
dao.deleteTable("permissions_entity_old");
dao.deleteTable("permissions_inheritance_old");
};
}
public static SchemaMigration oneToTwo() {
return dao -> {
// Change encoding for all columns to utf8mb4
// Change collation for all columns to utf8mb4_general_ci
dao.legacy().prepareStatement(dao, "ALTER TABLE `{permissions}` DROP KEY `unique`, MODIFY COLUMN `permission` TEXT NOT NULL").execute();
};
}
public static SchemaMigration zeroToOne() {
return dao -> {
PreparedStatement updateStmt = dao.prepareStatement(dao.legacy().getInsertOptionQuery());
ResultSet res = dao.legacy().prepareStatement(dao, "SELECT `name`, `type` FROM `{permissions_entity}` WHERE `default`='1'").executeQuery();
while (res.next()) {
updateStmt.setString(1, res.getString(1));
updateStmt.setInt(2, res.getInt(2));
updateStmt.setString(3, "default");
updateStmt.setString(4, "");
updateStmt.setString(5, "true");
updateStmt.addBatch();
}
updateStmt.executeBatch();
// Update tables
dao.prepareStatement("ALTER TABLE `{permissions_entity}` DROP COLUMN `default`").execute();
};
}
public static SchemaMigration initialToZero() {
return (LegacyMigration) dao -> {
// TODO: Table modifications not supported in SQLite
// Prefix/sufix -> options
PreparedStatement updateStmt = dao.legacy().prepareStatement(dao, dao.legacy().getInsertOptionQuery());
ResultSet res = dao.prepareStatement("SELECT `name`, `type`, `prefix`, `suffix` FROM `{permissions_entity}` WHERE LENGTH(`prefix`)>0 OR LENGTH(`suffix`)>0").executeQuery();
while (res.next()) {
String prefix = res.getString("prefix");
if (!prefix.isEmpty() && !prefix.equals("null")) {
updateStmt.setString(1, res.getString(1));
updateStmt.setInt(2, res.getInt(2));
updateStmt.setString(3, "prefix");
updateStmt.setString(4, "");
updateStmt.setString(5, prefix);
updateStmt.addBatch();
}
String suffix = res.getString("suffix");
if (!suffix.isEmpty() && !suffix.equals("null")) {
updateStmt.setString(1, res.getString(1));
updateStmt.setInt(2, res.getInt(2));
updateStmt.setString(3, "suffix");
updateStmt.setString(4, "");
updateStmt.setString(5, suffix);
updateStmt.addBatch();
}
}
updateStmt.executeBatch();
// Data type corrections
// Update tables
dao.prepareStatement("ALTER TABLE `{permissions_entity}` DROP KEY `name`").execute();
dao.prepareStatement("ALTER TABLE `{permissions_entity}` DROP COLUMN `prefix`, DROP COLUMN `suffix`").execute();
dao.prepareStatement("ALTER TABLE `{permissions_entity}` ADD CONSTRAINT UNIQUE KEY `name` (`name`, `type`)").execute();
dao.prepareStatement("ALTER TABLE `{permissions}` DROP KEY `unique`").execute();
dao.prepareStatement("ALTER TABLE `{permissions}` ADD CONSTRAINT UNIQUE `unique` (`name`,`permission`,`world`,`type`)").execute();
};
}
}