Make bulk update operations a bit more verbose (#2647)

This commit is contained in:
Federico López 2020-10-16 08:11:30 -03:00 committed by GitHub
parent 3b779dbbd3
commit 703b18e51c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 230 additions and 12 deletions

View File

@ -48,10 +48,15 @@ public final class BulkUpdate {
// a set of constraints which data must match to be acted upon
private final List<Query> queries;
public BulkUpdate(DataType dataType, Action action, List<Query> queries) {
// update statistics of the operation (number of nodes, users and groups affected)
private final BulkUpdateStatistics statistics = new BulkUpdateStatistics();
private final boolean trackStatistics;
public BulkUpdate(DataType dataType, Action action, List<Query> queries, boolean trackStatistics) {
this.dataType = dataType;
this.action = action;
this.queries = queries;
this.trackStatistics = trackStatistics;
}
/**
@ -131,6 +136,14 @@ public final class BulkUpdate {
return this.queries;
}
public boolean isTrackingStatistics() {
return trackStatistics;
}
public BulkUpdateStatistics getStatistics() {
return statistics;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
@ -144,7 +157,7 @@ public final class BulkUpdate {
@Override
public int hashCode() {
return Objects.hash(getDataType(), getAction(), getQueries());
return Objects.hash(getDataType(), getAction(), getQueries(), isTrackingStatistics());
}
@Override
@ -152,6 +165,7 @@ public final class BulkUpdate {
return "BulkUpdate(" +
"dataType=" + this.getDataType() + ", " +
"action=" + this.getAction() + ", " +
"constraints=" + this.getQueries() + ")";
"constraints=" + this.getQueries() + ", " +
"trackStatistics=" + this.isTrackingStatistics() + ")";
}
}

View File

@ -48,6 +48,9 @@ public class BulkUpdateBuilder {
// the action to apply to the data which matches the constraints
private Action action = null;
// should the operation count the number of affected nodes, users and groups
private boolean trackStatistics = false;
// a set of constraints which data must match to be acted upon
private final Set<Query> queries = new LinkedHashSet<>();
@ -64,6 +67,11 @@ public class BulkUpdateBuilder {
return this;
}
public BulkUpdateBuilder trackStatistics(boolean trackStatistics) {
this.trackStatistics = trackStatistics;
return this;
}
public BulkUpdateBuilder query(Query query) {
this.queries.add(query);
return this;
@ -74,7 +82,7 @@ public class BulkUpdateBuilder {
throw new IllegalStateException("no action specified");
}
return new BulkUpdate(this.dataType, this.action, ImmutableList.copyOf(this.queries));
return new BulkUpdate(this.dataType, this.action, ImmutableList.copyOf(this.queries), trackStatistics);
}
@Override
@ -82,6 +90,7 @@ public class BulkUpdateBuilder {
return "BulkUpdateBuilder(" +
"dataType=" + this.dataType + ", " +
"action=" + this.action + ", " +
"constraints=" + this.queries + ")";
"constraints=" + this.queries + ", " +
"trackStatistics=" + this.trackStatistics + ")";
}
}

View File

@ -0,0 +1,81 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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.bulkupdate;
/**
* Keeps track of the number of nodes, users and groups that were affected in a BulkUpdate operation.
*/
public final class BulkUpdateStatistics {
// the number of users that had their nodes updated/deleted due to the bulk update
private int affectedUsers = 0;
// the number of groups that had their nodes updated/deleted
private int affectedGroups = 0;
// the total number of affected nodes
private int affectedNodes = 0;
public BulkUpdateStatistics() {
}
public int getAffectedNodes() {
return affectedNodes;
}
public int getAffectedUsers() {
return affectedUsers;
}
public int getAffectedGroups() {
return affectedGroups;
}
public void incrementAffectedNodes() {
++affectedNodes;
}
public void incrementAffectedUsers() {
++affectedUsers;
}
public void incrementAffectedGroups() {
++affectedGroups;
}
public void incrementAffectedNodesBy(int delta) {
affectedNodes += delta;
}
public void incrementAffectedUsersBy(int delta) {
affectedUsers += delta;
}
public void incrementAffectedGroupsBy(int delta) {
affectedGroups += delta;
}
}

View File

@ -29,6 +29,7 @@ import com.github.benmanes.caffeine.cache.Cache;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateBuilder;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateStatistics;
import me.lucko.luckperms.common.bulkupdate.DataType;
import me.lucko.luckperms.common.bulkupdate.action.DeleteAction;
import me.lucko.luckperms.common.bulkupdate.action.UpdateAction;
@ -81,6 +82,10 @@ public class BulkUpdateCommand extends SingleCommand {
if (ex == null) {
plugin.getSyncTaskBuffer().requestDirectly();
Message.BULK_UPDATE_SUCCESS.send(sender);
if (operation.isTrackingStatistics()) {
BulkUpdateStatistics stats = operation.getStatistics();
Message.BULK_UPDATE_STATISTICS.send(sender, stats.getAffectedNodes(), stats.getAffectedUsers(), stats.getAffectedGroups());
}
} else {
ex.printStackTrace();
Message.BULK_UPDATE_FAILURE.send(sender);
@ -95,6 +100,8 @@ public class BulkUpdateCommand extends SingleCommand {
BulkUpdateBuilder bulkUpdateBuilder = BulkUpdateBuilder.create();
bulkUpdateBuilder.trackStatistics(!args.remove("--silent"));
try {
bulkUpdateBuilder.dataType(DataType.valueOf(args.remove(0).toUpperCase()));
} catch (IllegalArgumentException e) {

View File

@ -2563,6 +2563,27 @@ public interface Message {
.append(FULL_STOP)
);
Args3<Integer, Integer, Integer> BULK_UPDATE_STATISTICS = (nodes, users, groups) -> join(newline(),
// "&bTotal affected nodes: &a{}"
// "&bTotal affected users: &a{}"
// "&bTotal affected groups: &a{}"
prefixed(translatable()
.key("luckperms.command.bulkupdate.success.statistics.nodes")
.color(AQUA)
.append(text(": "))
.append(text(nodes, GREEN))),
prefixed(translatable()
.key("luckperms.command.bulkupdate.success.statistics.users")
.color(AQUA)
.append(text(": "))
.append(text(users, GREEN))),
prefixed(translatable()
.key("luckperms.command.bulkupdate.success.statistics.groups")
.color(AQUA)
.append(text(": "))
.append(text(groups, GREEN)))
);
Args0 BULK_UPDATE_FAILURE = () -> prefixed(translatable()
// "&cBulk update failed, check the console for errors."
.key("luckperms.command.bulkupdate.failure")

View File

@ -31,9 +31,12 @@ import com.google.common.collect.Maps;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateStatistics;
import me.lucko.luckperms.common.context.ContextSetConfigurateSerializer;
import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.model.PermissionHolderIdentifier;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.model.manager.group.GroupManager;
@ -180,13 +183,29 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
return this.actionLogger.getLog();
}
protected ConfigurationNode processBulkUpdate(BulkUpdate bulkUpdate, ConfigurationNode node) {
protected ConfigurationNode processBulkUpdate(BulkUpdate bulkUpdate, ConfigurationNode node, HolderType holderType) {
BulkUpdateStatistics stats = bulkUpdate.getStatistics();
Set<Node> nodes = readNodes(node);
Set<Node> results = nodes.stream()
.map(bulkUpdate::apply)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (bulkUpdate.isTrackingStatistics() && !results.isEmpty()) {
stats.incrementAffectedNodesBy(results.size());
switch (holderType) {
case USER:
stats.incrementAffectedUsers();
break;
case GROUP:
stats.incrementAffectedGroups();
break;
}
}
if (nodes.equals(results)) {
return null;
}

View File

@ -26,6 +26,7 @@
package me.lucko.luckperms.common.storage.implementation.file;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.implementation.file.loader.ConfigurateLoader;
@ -240,7 +241,7 @@ public class CombinedConfigurateStorage extends AbstractConfigurateStorage {
if (bulkUpdate.getDataType().isIncludingUsers()) {
this.usersLoader.apply(true, true, root -> {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
processBulkUpdate(bulkUpdate, entry.getValue());
processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.USER);
}
});
}
@ -248,7 +249,7 @@ public class CombinedConfigurateStorage extends AbstractConfigurateStorage {
if (bulkUpdate.getDataType().isIncludingGroups()) {
this.groupsLoader.apply(true, true, root -> {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
processBulkUpdate(bulkUpdate, entry.getValue());
processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.GROUP);
}
});
}

View File

@ -26,6 +26,7 @@
package me.lucko.luckperms.common.storage.implementation.file;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@ -212,7 +213,7 @@ public class SeparatedConfigurateStorage extends AbstractConfigurateStorage {
try {
registerFileAction(StorageLocation.USER, file);
ConfigurationNode object = readFile(file);
ConfigurationNode results = processBulkUpdate(bulkUpdate, object);
ConfigurationNode results = processBulkUpdate(bulkUpdate, object, HolderType.USER);
if (results != null) {
saveFile(file, object);
}
@ -229,7 +230,7 @@ public class SeparatedConfigurateStorage extends AbstractConfigurateStorage {
try {
registerFileAction(StorageLocation.GROUP, file);
ConfigurationNode object = readFile(file);
ConfigurationNode results = processBulkUpdate(bulkUpdate, object);
ConfigurationNode results = processBulkUpdate(bulkUpdate, object, HolderType.GROUP);
if (results != null) {
saveFile(file, object);
}

View File

@ -40,6 +40,7 @@ import com.mongodb.client.model.ReplaceOptions;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateStatistics;
import me.lucko.luckperms.common.context.contextset.MutableContextSetImpl;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
@ -243,6 +244,8 @@ public class MongoStorage implements StorageImplementation {
@Override
public void applyBulkUpdate(BulkUpdate bulkUpdate) {
BulkUpdateStatistics stats = bulkUpdate.getStatistics();
if (bulkUpdate.getDataType().isIncludingUsers()) {
MongoCollection<Document> c = this.database.getCollection(this.prefix + "users");
try (MongoCursor<Document> cursor = c.find().iterator()) {
@ -256,6 +259,11 @@ public class MongoStorage implements StorageImplementation {
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (bulkUpdate.isTrackingStatistics() && !results.isEmpty()) {
stats.incrementAffectedUsers();
stats.incrementAffectedNodesBy(results.size());
}
if (!nodes.equals(results)) {
List<Document> newNodes = results.stream()
.map(MongoStorage::nodeToDoc)
@ -281,6 +289,11 @@ public class MongoStorage implements StorageImplementation {
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (bulkUpdate.isTrackingStatistics() && !results.isEmpty()) {
stats.incrementAffectedGroups();
stats.incrementAffectedNodesBy(results.size());
}
if (!nodes.equals(results)) {
List<Document> newNodes = results.stream()
.map(MongoStorage::nodeToDoc)

View File

@ -31,6 +31,7 @@ import com.google.gson.reflect.TypeToken;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateStatistics;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import me.lucko.luckperms.common.context.ContextSetJsonSerializer;
import me.lucko.luckperms.common.model.Group;
@ -249,18 +250,66 @@ public class SqlStorage implements StorageImplementation {
@Override
public void applyBulkUpdate(BulkUpdate bulkUpdate) throws SQLException {
BulkUpdateStatistics stats = bulkUpdate.getStatistics();
try (Connection c = this.connectionFactory.getConnection()) {
if (bulkUpdate.getDataType().isIncludingUsers()) {
String table = this.statementProcessor.apply("{prefix}user_permissions");
try (PreparedStatement ps = bulkUpdate.buildAsSql().build(c, q -> q.replace("{table}", table))) {
ps.execute();
if (bulkUpdate.isTrackingStatistics()) {
PreparedStatementBuilder builder = new PreparedStatementBuilder();
builder.append(USER_PERMISSIONS_SELECT_DISTINCT);
if (!bulkUpdate.getQueries().isEmpty()) {
builder.append(" WHERE ");
bulkUpdate.getQueries().forEach(query -> query.appendSql(builder));
}
try (PreparedStatement lookup = builder.build(c, this.statementProcessor)) {
try (ResultSet rs = lookup.executeQuery()) {
Set<UUID> uuids = new HashSet<>();
while (rs.next()) {
uuids.add(Uuids.fromString(rs.getString("uuid")));
}
uuids.remove(null);
stats.incrementAffectedUsersBy(uuids.size());
}
}
stats.incrementAffectedNodesBy(ps.executeUpdate());
} else {
ps.execute();
}
}
}
if (bulkUpdate.getDataType().isIncludingGroups()) {
String table = this.statementProcessor.apply("{prefix}group_permissions");
try (PreparedStatement ps = bulkUpdate.buildAsSql().build(c, q -> q.replace("{table}", table))) {
ps.execute();
if (bulkUpdate.isTrackingStatistics()) {
PreparedStatementBuilder builder = new PreparedStatementBuilder();
builder.append(GROUP_PERMISSIONS_SELECT_ALL);
if (!bulkUpdate.getQueries().isEmpty()) {
builder.append(" WHERE ");
bulkUpdate.getQueries().forEach(query -> query.appendSql(builder));
}
try (PreparedStatement lookup = builder.build(c, this.statementProcessor)) {
try (ResultSet rs = lookup.executeQuery()) {
Set<String> groups = new HashSet<>();
while (rs.next()) {
groups.add(rs.getString("name"));
}
groups.remove(null);
stats.incrementAffectedGroupsBy(groups.size());
}
}
stats.incrementAffectedNodesBy(ps.executeUpdate());
} else {
ps.execute();
}
}
}
}

View File

@ -293,6 +293,9 @@ luckperms.command.bulkupdate.confirm=Run {0} to execute the update
luckperms.command.bulkupdate.unknown-id=Operation with id {0} does not exist or has expired
luckperms.command.bulkupdate.starting=Running bulk update
luckperms.command.bulkupdate.success=Bulk update completed successfully
luckperms.command.bulkupdate.success.statistics.nodes=Total affected nodes
luckperms.command.bulkupdate.success.statistics.users=Total affected users
luckperms.command.bulkupdate.success.statistics.groups=Total affected groups
luckperms.command.bulkupdate.failure=Bulk update failed, check the console for errors
luckperms.command.update-task.request=An update task has been requested, please wait
luckperms.command.update-task.complete=Update task complete