Fix user_id reference error on ShutdownDataPreservationTransaction

This commit is contained in:
Aurora Lahtela 2022-05-26 08:16:00 +03:00
parent f3a5e27d8d
commit 17d39f518d
5 changed files with 147 additions and 13 deletions

View File

@ -22,8 +22,7 @@ import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.lang.PluginLang; import com.djrapitops.plan.settings.locale.lang.PluginLang;
import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.LargeStoreQueries; import com.djrapitops.plan.storage.database.transactions.events.ShutdownDataPreservationTransaction;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import com.djrapitops.plan.storage.file.PlanFiles; import com.djrapitops.plan.storage.file.PlanFiles;
import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger; import com.djrapitops.plan.utilities.logging.ErrorLogger;
@ -87,12 +86,7 @@ public class ShutdownDataPreservation extends TaskSystem.Task {
private void storeInDB(List<FinishedSession> finishedSessions) { private void storeInDB(List<FinishedSession> finishedSessions) {
if (!finishedSessions.isEmpty()) { if (!finishedSessions.isEmpty()) {
try { try {
dbSystem.getDatabase().executeTransaction(new Transaction() { dbSystem.getDatabase().executeTransaction(new ShutdownDataPreservationTransaction(finishedSessions)).get();
@Override
protected void performOperations() {
execute(LargeStoreQueries.storeAllSessionsWithKillAndWorldData(finishedSessions));
}
}).get();
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} catch (ExecutionException e) { } catch (ExecutionException e) {

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.gathering.domain; package com.djrapitops.plan.gathering.domain;
import com.djrapitops.plan.delivery.domain.DateHolder; import com.djrapitops.plan.delivery.domain.DateHolder;
import com.djrapitops.plan.delivery.domain.PlayerName;
import com.djrapitops.plan.gathering.domain.event.JoinAddress; import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable; import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
@ -143,6 +144,7 @@ public class FinishedSession implements DateHolder {
asOptionals.get(7).ifPresent(value -> extraData.put(MobKillCounter.class, gson.fromJson(value, MobKillCounter.class))); asOptionals.get(7).ifPresent(value -> extraData.put(MobKillCounter.class, gson.fromJson(value, MobKillCounter.class)));
asOptionals.get(8).ifPresent(value -> extraData.put(DeathCounter.class, gson.fromJson(value, DeathCounter.class))); asOptionals.get(8).ifPresent(value -> extraData.put(DeathCounter.class, gson.fromJson(value, DeathCounter.class)));
asOptionals.get(9).ifPresent(value -> extraData.put(JoinAddress.class, new JoinAddress(value))); asOptionals.get(9).ifPresent(value -> extraData.put(JoinAddress.class, new JoinAddress(value)));
asOptionals.get(10).ifPresent(value -> extraData.put(PlayerName.class, new PlayerName(value)));
return Optional.of(new FinishedSession(playerUUID, serverUUID, start, end, afkTime, extraData)); return Optional.of(new FinishedSession(playerUUID, serverUUID, start, end, afkTime, extraData));
} }
@ -194,7 +196,8 @@ public class FinishedSession implements DateHolder {
getExtraData(PlayerKills.class).orElseGet(PlayerKills::new).toJson() + ';' + getExtraData(PlayerKills.class).orElseGet(PlayerKills::new).toJson() + ';' +
getExtraData(MobKillCounter.class).orElseGet(MobKillCounter::new).toJson() + ';' + getExtraData(MobKillCounter.class).orElseGet(MobKillCounter::new).toJson() + ';' +
getExtraData(DeathCounter.class).orElseGet(DeathCounter::new).toJson() + ';' + getExtraData(DeathCounter.class).orElseGet(DeathCounter::new).toJson() + ';' +
getExtraData(JoinAddress.class).orElseGet(() -> new JoinAddress(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)).getAddress(); getExtraData(JoinAddress.class).map(JoinAddress::getAddress).orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP) + ';' +
getExtraData(PlayerName.class).map(PlayerName::get).orElseGet(playerUUID::toString);
} }
public static class Id { public static class Id {

View File

@ -23,6 +23,7 @@ import com.djrapitops.plan.storage.database.queries.QueryAllStatement;
import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.queries.QueryStatement;
import com.djrapitops.plan.storage.database.sql.building.Select; import com.djrapitops.plan.storage.database.sql.building.Select;
import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
import org.apache.commons.text.TextStringBuilder;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -160,4 +161,21 @@ public class BaseUserQueries {
} }
}; };
} }
public static Query<Set<UUID>> fetchExistingUUIDs(Set<UUID> outOfPlayerUUIDs) {
String sql = SELECT + UsersTable.USER_UUID +
FROM + UsersTable.TABLE_NAME +
WHERE + UsersTable.USER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(outOfPlayerUUIDs, "','").build() + "')";
return new QueryAllStatement<Set<UUID>>(sql) {
@Override
public Set<UUID> processResults(ResultSet set) throws SQLException {
Set<UUID> uuids = new HashSet<>();
while (set.next()) {
uuids.add(UUID.fromString(set.getString(UsersTable.USER_UUID)));
}
return uuids;
}
};
}
} }

View File

@ -0,0 +1,83 @@
/*
* 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.storage.database.transactions.events;
import com.djrapitops.plan.delivery.domain.PlayerName;
import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.gathering.domain.PlayerKill;
import com.djrapitops.plan.gathering.domain.PlayerKills;
import com.djrapitops.plan.storage.database.queries.LargeStoreQueries;
import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import java.util.*;
import java.util.function.LongSupplier;
public class ShutdownDataPreservationTransaction extends Transaction {
private final List<FinishedSession> finishedSessions;
public ShutdownDataPreservationTransaction(List<FinishedSession> finishedSessions) {
this.finishedSessions = finishedSessions;
}
@Override
protected void performOperations() {
ensureAllPlayersAreRegistered();
execute(LargeStoreQueries.storeAllSessionsWithKillAndWorldData(finishedSessions));
}
private void ensureAllPlayersAreRegistered() {
Set<UUID> playerUUIDs = new HashSet<>();
Map<UUID, String> playerNames = new HashMap<>();
Map<UUID, Long> earliestDates = new HashMap<>();
for (FinishedSession finishedSession : finishedSessions) {
UUID playerUUID = finishedSession.getPlayerUUID();
playerUUIDs.add(playerUUID);
finishedSession.getExtraData(PlayerKills.class)
.map(PlayerKills::asList)
.ifPresent(kills -> {
for (PlayerKill kill : kills) {
playerUUIDs.add(kill.getKiller().getUuid());
playerUUIDs.add(kill.getVictim().getUuid());
}
});
finishedSession.getExtraData(PlayerName.class)
.map(PlayerName::get)
.ifPresent(playerName -> playerNames.put(playerUUID, playerName));
long start = finishedSession.getStart();
Long previous = earliestDates.get(playerUUID);
if (previous == null || start < previous) {
earliestDates.put(playerUUID, start);
}
}
Set<UUID> existingUUIDs = query(BaseUserQueries.fetchExistingUUIDs(playerUUIDs));
for (UUID playerUUID : playerUUIDs) {
if (!existingUUIDs.contains(playerUUID)) {
LongSupplier registerDate = () -> Optional.ofNullable(earliestDates.get(playerUUID))
.orElseGet(System::currentTimeMillis);
String playerName = Optional.ofNullable(playerNames.get(playerUUID))
.orElseGet(playerUUID::toString);
executeOther(new PlayerRegisterTransaction(playerUUID, registerDate, playerName));
}
}
}
}

View File

@ -34,10 +34,7 @@ import com.djrapitops.plan.storage.database.transactions.ExecStatement;
import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction; import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction;
import com.djrapitops.plan.storage.database.transactions.Transaction; import com.djrapitops.plan.storage.database.transactions.Transaction;
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction; import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
import com.djrapitops.plan.storage.database.transactions.events.PlayerRegisterTransaction; import com.djrapitops.plan.storage.database.transactions.events.*;
import com.djrapitops.plan.storage.database.transactions.events.PlayerServerRegisterTransaction;
import com.djrapitops.plan.storage.database.transactions.events.SessionEndTransaction;
import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction;
import com.djrapitops.plan.utilities.java.Maps; import com.djrapitops.plan.utilities.java.Maps;
import net.playeranalytics.plugin.scheduling.TimeAmount; import net.playeranalytics.plugin.scheduling.TimeAmount;
import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.RepeatedTest;
@ -97,6 +94,45 @@ public interface SessionQueriesTest extends DatabaseTestPreparer {
assertEquals(session, savedSessions.get(0)); assertEquals(session, savedSessions.get(0));
} }
@Test
default void shutdownDataPreservationTransactionOutOfOrderDoesNotFailDueToMissingMainUser() {
db().executeTransaction(new WorldNameStoreTransaction(serverUUID(), worlds[0]));
db().executeTransaction(new WorldNameStoreTransaction(serverUUID(), worlds[1]));
db().executeTransaction(new PlayerServerRegisterTransaction(player2UUID, RandomData::randomTime,
TestConstants.PLAYER_TWO_NAME, serverUUID(), TestConstants.GET_PLAYER_HOSTNAME));
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
db().executeTransaction(new ShutdownDataPreservationTransaction(List.of(session)));
Map<ServerUUID, List<FinishedSession>> sessions = db().query(SessionQueries.fetchSessionsOfPlayer(playerUUID));
List<FinishedSession> savedSessions = sessions.get(serverUUID());
assertNotNull(savedSessions);
assertEquals(1, savedSessions.size());
assertEquals(session, savedSessions.get(0));
}
@Test
default void shutdownDataPreservationTransactionOutOfOrderDoesNotFailDueToMissingKilledUser() {
db().executeTransaction(new WorldNameStoreTransaction(serverUUID(), worlds[0]));
db().executeTransaction(new WorldNameStoreTransaction(serverUUID(), worlds[1]));
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, RandomData::randomTime,
TestConstants.PLAYER_ONE_NAME, serverUUID(), TestConstants.GET_PLAYER_HOSTNAME));
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
db().executeTransaction(new ShutdownDataPreservationTransaction(List.of(session)));
Map<ServerUUID, List<FinishedSession>> sessions = db().query(SessionQueries.fetchSessionsOfPlayer(playerUUID));
List<FinishedSession> savedSessions = sessions.get(serverUUID());
assertNotNull(savedSessions);
assertEquals(1, savedSessions.size());
assertEquals(session, savedSessions.get(0));
}
@Test @Test
default void killsAreAvailableAfter2ndUserRegisterEvenIfOutOfOrder() { default void killsAreAvailableAfter2ndUserRegisterEvenIfOutOfOrder() {
db().executeTransaction(new WorldNameStoreTransaction(serverUUID(), worlds[0])); db().executeTransaction(new WorldNameStoreTransaction(serverUUID(), worlds[0]));