mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-12-27 19:47:49 +01:00
Transaction for removing unsatisfied Conditional data:
This is one of the most complex queries I have made. - Select all fulfilled conditions for all players (conditionName when true and not_conditionName when false) - Left join with player value & provider tables when uuids match, and when condition matches a condition in the query above. - Filter the join query for values where the condition did not match any provided condition in the join (Is null) - Delete all player values with IDs that are returned by the left join query after filtering In addition: - Added test for the transaction - Added extension data removal to RemoveEverythingTransaction - Added unregister method to ExtensionService
This commit is contained in:
parent
09ac2dce09
commit
47a6a9b2aa
@ -58,6 +58,16 @@ public interface ExtensionService {
|
||||
*/
|
||||
void register(DataExtension extension);
|
||||
|
||||
/**
|
||||
* Unregister your {@link DataExtension} implementation.
|
||||
* <p>
|
||||
* This method should be used if calling methods on the DataExtension suddenly becomes unavailable, due to
|
||||
* plugin disable for example.
|
||||
*
|
||||
* @param extension Your DataExtension implementation that was registered before.
|
||||
*/
|
||||
void unregister(DataExtension extension);
|
||||
|
||||
class ExtensionServiceHolder {
|
||||
static ExtensionService API;
|
||||
|
||||
|
@ -26,6 +26,7 @@ import com.djrapitops.plan.db.access.transactions.init.CreateIndexTransaction;
|
||||
import com.djrapitops.plan.db.access.transactions.init.CreateTablesTransaction;
|
||||
import com.djrapitops.plan.db.access.transactions.init.OperationCriticalTransaction;
|
||||
import com.djrapitops.plan.db.patches.*;
|
||||
import com.djrapitops.plan.system.DebugChannels;
|
||||
import com.djrapitops.plan.system.locale.Locale;
|
||||
import com.djrapitops.plan.system.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.system.settings.paths.TimeSettings;
|
||||
@ -219,6 +220,7 @@ public abstract class SQLDB extends AbstractDatabase {
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
accessLock.checkAccess(transaction);
|
||||
logger.getDebugLogger().logOn(DebugChannels.SQL, "Executing: " + transaction.getClass().getSimpleName());
|
||||
transaction.executeTransaction(this);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}, getTransactionExecutor()).handle(errorHandler(origin));
|
||||
|
@ -44,6 +44,11 @@ public class RemoveEverythingTransaction extends Transaction {
|
||||
clearTable(TPSTable.TABLE_NAME);
|
||||
clearTable(SecurityTable.TABLE_NAME);
|
||||
clearTable(ServerTable.TABLE_NAME);
|
||||
clearTable(ExtensionPlayerValueTable.TABLE_NAME);
|
||||
clearTable(ExtensionProviderTable.TABLE_NAME);
|
||||
clearTable(ExtensionTabTable.TABLE_NAME);
|
||||
clearTable(ExtensionPluginTable.TABLE_NAME);
|
||||
clearTable(ExtensionIconTable.TABLE_NAME);
|
||||
}
|
||||
|
||||
private void clearTable(String tableName) {
|
||||
|
@ -27,6 +27,7 @@ import com.djrapitops.plan.db.access.transactions.commands.RemovePlayerTransacti
|
||||
import com.djrapitops.plan.db.sql.tables.PingTable;
|
||||
import com.djrapitops.plan.db.sql.tables.SessionsTable;
|
||||
import com.djrapitops.plan.db.sql.tables.TPSTable;
|
||||
import com.djrapitops.plan.extension.implementation.storage.transactions.results.RemoveUnsatisfiedConditionalResultsTransaction;
|
||||
import com.djrapitops.plan.system.locale.Locale;
|
||||
import com.djrapitops.plan.system.locale.lang.PluginLang;
|
||||
import com.djrapitops.plugin.api.TimeAmount;
|
||||
@ -73,6 +74,9 @@ public class CleanTransaction extends Transaction {
|
||||
execute(cleanTPSTable(allTimePeak.orElse(-1)));
|
||||
execute(cleanPingTable());
|
||||
|
||||
// Clean DataExtension data
|
||||
executeOther(new RemoveUnsatisfiedConditionalResultsTransaction());
|
||||
|
||||
int removed = cleanOldPlayers();
|
||||
if (removed > 0) {
|
||||
logger.info(locale.getString(PluginLang.DB_NOTIFY_CLEAN, removed));
|
||||
|
@ -31,6 +31,7 @@ public class Sql {
|
||||
public static final String INNER_JOIN = " INNER JOIN ";
|
||||
public static final String LEFT_JOIN = " LEFT JOIN ";
|
||||
public static final String AND = " AND ";
|
||||
public static final String OR = " OR ";
|
||||
|
||||
private Sql() {
|
||||
throw new IllegalStateException("Variable Class");
|
||||
|
@ -95,6 +95,16 @@ public class ExtensionServiceImplementation implements ExtensionService {
|
||||
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, pluginName + " extension registered.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(DataExtension extension) {
|
||||
DataProviderExtractor extractor = new DataProviderExtractor(extension);
|
||||
String pluginName = extractor.getPluginName();
|
||||
if (extensionGatherers.remove(pluginName) != null) {
|
||||
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, pluginName + " extension unregistered.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean shouldNotAllowRegistration(String pluginName) {
|
||||
PluginsConfigSection pluginsConfig = config.getPluginsConfigSection();
|
||||
|
||||
@ -118,7 +128,11 @@ public class ExtensionServiceImplementation implements ExtensionService {
|
||||
public void updatePlayerValues(UUID playerUUID, String playerName) {
|
||||
for (Map.Entry<String, ProviderValueGatherer> gatherer : extensionGatherers.entrySet()) {
|
||||
try {
|
||||
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, "Gathering values for: " + playerName);
|
||||
|
||||
gatherer.getValue().updateValues(playerUUID, playerName);
|
||||
|
||||
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, "Gathering completed: " + playerName);
|
||||
} catch (Exception | NoClassDefFoundError | NoSuchMethodError | NoSuchFieldError e) {
|
||||
logger.warn(gatherer.getKey() + " ran into (but failed safely) " + e.getClass().getSimpleName() +
|
||||
" when updating value for '" + playerName +
|
||||
|
@ -75,7 +75,6 @@ public class ProviderValueGatherer {
|
||||
}
|
||||
|
||||
database.executeTransaction(new RemoveInvalidResultsTransaction(pluginName, serverUUID, extractor.getInvalidatedMethods()));
|
||||
// TODO remove data in db that are updated with a 'false' condition
|
||||
}
|
||||
|
||||
public void updateValues(UUID playerUUID, String playerName) {
|
||||
|
@ -135,4 +135,11 @@ public class ExtensionTabData implements Comparable<ExtensionTabData> {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExtensionTabData{" +
|
||||
"available=" + order +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
|
||||
|
||||
import com.djrapitops.plan.db.DBType;
|
||||
import com.djrapitops.plan.db.access.ExecStatement;
|
||||
import com.djrapitops.plan.db.access.Executable;
|
||||
import com.djrapitops.plan.db.access.transactions.Transaction;
|
||||
import com.djrapitops.plan.db.sql.tables.ExtensionPlayerValueTable;
|
||||
import com.djrapitops.plan.db.sql.tables.ExtensionProviderTable;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
|
||||
|
||||
/**
|
||||
* Transaction to remove older results that violate an updated condition value.
|
||||
* <p>
|
||||
* How it works:
|
||||
* - Select all fulfilled conditions for all players (conditionName when true and not_conditionName when false)
|
||||
* - Left join with player value & provider tables when uuids match, and when condition matches a condition in the query above.
|
||||
* - Filter the join query for values where the condition did not match any provided condition in the join (Is null)
|
||||
* - Delete all player values with IDs that are returned by the left join query after filtering
|
||||
*
|
||||
* @author Rsl1122
|
||||
*/
|
||||
public class RemoveUnsatisfiedConditionalResultsTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
execute(deleteUnsatisfied());
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfied() {
|
||||
String reversedCondition = dbType == DBType.SQLITE ? "'not_' || " + ExtensionProviderTable.PROVIDED_CONDITION : "CONCAT('not_'," + ExtensionProviderTable.PROVIDED_CONDITION + ')';
|
||||
|
||||
String providerTable = ExtensionProviderTable.TABLE_NAME;
|
||||
String playerValueTable = ExtensionPlayerValueTable.TABLE_NAME;
|
||||
|
||||
String selectSatisfiedPositiveConditions = SELECT +
|
||||
ExtensionProviderTable.PROVIDED_CONDITION + ',' +
|
||||
ExtensionPlayerValueTable.USER_UUID +
|
||||
FROM + playerValueTable +
|
||||
INNER_JOIN + providerTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
|
||||
WHERE + ExtensionPlayerValueTable.BOOLEAN_VALUE + "=?" +
|
||||
AND + ExtensionProviderTable.PROVIDED_CONDITION + " IS NOT NULL";
|
||||
String selectSatisfiedNegativeConditions = SELECT +
|
||||
reversedCondition + " as " + ExtensionProviderTable.PROVIDED_CONDITION + ',' +
|
||||
ExtensionPlayerValueTable.USER_UUID +
|
||||
FROM + playerValueTable +
|
||||
INNER_JOIN + providerTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
|
||||
WHERE + ExtensionPlayerValueTable.BOOLEAN_VALUE + "=?" +
|
||||
AND + ExtensionProviderTable.PROVIDED_CONDITION + " IS NOT NULL";
|
||||
|
||||
String selectSatisfiedConditions = '(' + selectSatisfiedPositiveConditions + " UNION " + selectSatisfiedNegativeConditions + ") q1";
|
||||
|
||||
String selectUnsatisfiedValueIDs = SELECT + playerValueTable + '.' + ExtensionPlayerValueTable.ID +
|
||||
FROM + playerValueTable +
|
||||
INNER_JOIN + providerTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
|
||||
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserver values that don't have their condition fulfilled
|
||||
" on (" +
|
||||
playerValueTable + '.' + ExtensionPlayerValueTable.USER_UUID +
|
||||
"=q1." + ExtensionPlayerValueTable.USER_UUID +
|
||||
AND + ExtensionProviderTable.CONDITION +
|
||||
"=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
|
||||
')' +
|
||||
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + " IS NULL" + // Conditions that were not in the satisfied condition query
|
||||
AND + ExtensionProviderTable.CONDITION + " IS NOT NULL"; // Ignore values that don't need condition
|
||||
|
||||
String sql = "DELETE FROM " + playerValueTable +
|
||||
WHERE + ExtensionPlayerValueTable.ID + " IN (" + selectUnsatisfiedValueIDs + ')';
|
||||
|
||||
return new ExecStatement(sql) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setBoolean(1, true); // Select provided conditions with 'true' value
|
||||
statement.setBoolean(2, false); // Select negated conditions with 'false' value
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -52,6 +52,7 @@ import com.djrapitops.plan.extension.implementation.results.player.ExtensionPlay
|
||||
import com.djrapitops.plan.extension.implementation.results.player.ExtensionStringData;
|
||||
import com.djrapitops.plan.extension.implementation.results.player.ExtensionTabData;
|
||||
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionPlayerDataQuery;
|
||||
import com.djrapitops.plan.extension.implementation.storage.transactions.results.RemoveUnsatisfiedConditionalResultsTransaction;
|
||||
import com.djrapitops.plan.system.PlanSystem;
|
||||
import com.djrapitops.plan.system.database.DBSystem;
|
||||
import com.djrapitops.plan.system.info.server.Server;
|
||||
@ -79,6 +80,7 @@ import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -153,6 +155,9 @@ public abstract class CommonDBTest {
|
||||
|
||||
db.executeTransaction(new StoreServerInformationTransaction(new Server(-1, serverUUID, "ServerName", "", 20)));
|
||||
assertEquals(serverUUID, db.getServerUUIDSupplier().get());
|
||||
|
||||
system.getExtensionService().unregister(new TestExtension());
|
||||
system.getExtensionService().unregister(new ConditionalExtension());
|
||||
}
|
||||
|
||||
private void execute(Executable executable) {
|
||||
@ -1051,6 +1056,58 @@ public abstract class CommonDBTest {
|
||||
OptionalAssert.equals("Something", tabData.getString("stringVal").map(ExtensionStringData::getFormattedValue));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsatisfiedConditionalResultsAreCleaned() throws ExecutionException, InterruptedException {
|
||||
ExtensionServiceImplementation extensionService = (ExtensionServiceImplementation) system.getExtensionService();
|
||||
|
||||
extensionService.register(new ConditionalExtension());
|
||||
|
||||
ConditionalExtension.condition = true;
|
||||
extensionService.updatePlayerValues(playerUUID, TestConstants.PLAYER_ONE_NAME);
|
||||
|
||||
// Check that the wanted data exists
|
||||
checkThatDataExists(ConditionalExtension.condition);
|
||||
|
||||
// Reverse condition
|
||||
ConditionalExtension.condition = false;
|
||||
extensionService.updatePlayerValues(playerUUID, TestConstants.PLAYER_ONE_NAME);
|
||||
|
||||
db.executeTransaction(new RemoveUnsatisfiedConditionalResultsTransaction());
|
||||
|
||||
// Check that the wanted data exists
|
||||
checkThatDataExists(ConditionalExtension.condition);
|
||||
|
||||
// Reverse condition
|
||||
ConditionalExtension.condition = false;
|
||||
extensionService.updatePlayerValues(playerUUID, TestConstants.PLAYER_ONE_NAME);
|
||||
|
||||
db.executeTransaction(new RemoveUnsatisfiedConditionalResultsTransaction());
|
||||
|
||||
// Check that the wanted data exists
|
||||
checkThatDataExists(ConditionalExtension.condition);
|
||||
}
|
||||
|
||||
private void checkThatDataExists(boolean condition) {
|
||||
if (condition) { // Condition is true, conditional values exist
|
||||
List<ExtensionPlayerData> ofServer = db.query(new ExtensionPlayerDataQuery(playerUUID)).get(serverUUID);
|
||||
assertTrue("There was no data left", ofServer != null && !ofServer.isEmpty() && !ofServer.get(0).getTabs().isEmpty());
|
||||
|
||||
ExtensionTabData tabData = ofServer.get(0).getTabs().get(0);
|
||||
OptionalAssert.equals("Yes", tabData.getBoolean("isCondition").map(ExtensionBooleanData::getFormattedValue));
|
||||
OptionalAssert.equals("Conditional", tabData.getString("conditionalValue").map(ExtensionStringData::getFormattedValue));
|
||||
OptionalAssert.equals("unconditional", tabData.getString("unconditional").map(ExtensionStringData::getFormattedValue)); // Was not removed
|
||||
assertFalse("Value was not removed: reversedConditionalValue", tabData.getString("reversedConditionalValue").isPresent());
|
||||
} else { // Condition is false, reversed conditional values exist
|
||||
List<ExtensionPlayerData> ofServer = db.query(new ExtensionPlayerDataQuery(playerUUID)).get(serverUUID);
|
||||
assertTrue("There was no data left", ofServer != null && !ofServer.isEmpty() && !ofServer.get(0).getTabs().isEmpty());
|
||||
ExtensionTabData tabData = ofServer.get(0).getTabs().get(0);
|
||||
OptionalAssert.equals("No", tabData.getBoolean("isCondition").map(ExtensionBooleanData::getFormattedValue));
|
||||
OptionalAssert.equals("Reversed", tabData.getString("reversedConditionalValue").map(ExtensionStringData::getFormattedValue));
|
||||
OptionalAssert.equals("unconditional", tabData.getString("unconditional").map(ExtensionStringData::getFormattedValue)); // Was not removed
|
||||
assertFalse("Value was not removed: conditionalValue", tabData.getString("conditionalValue").isPresent());
|
||||
}
|
||||
}
|
||||
|
||||
@PluginInfo(name = "TestExtension")
|
||||
public class TestExtension implements DataExtension {
|
||||
@NumberProvider(text = "a number")
|
||||
@ -1078,4 +1135,32 @@ public abstract class CommonDBTest {
|
||||
return "Something";
|
||||
}
|
||||
}
|
||||
|
||||
@PluginInfo(name = "Conditional TestExtension")
|
||||
public static class ConditionalExtension implements DataExtension {
|
||||
|
||||
static boolean condition = true;
|
||||
|
||||
@BooleanProvider(text = "a boolean", conditionName = "condition")
|
||||
public boolean isCondition(UUID playerUUID) {
|
||||
return condition;
|
||||
}
|
||||
|
||||
@StringProvider(text = "Conditional Value")
|
||||
@Conditional("condition")
|
||||
public String conditionalValue(UUID playerUUID) {
|
||||
return "Conditional";
|
||||
}
|
||||
|
||||
@StringProvider(text = "Reversed Conditional Value")
|
||||
@Conditional(value = "condition", negated = true)
|
||||
public String reversedConditionalValue(UUID playerUUID) {
|
||||
return "Reversed";
|
||||
}
|
||||
|
||||
@StringProvider(text = "Unconditional")
|
||||
public String unconditional(UUID playerUUID) {
|
||||
return "unconditional";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user