Store join addresses separately and link to plan_sessions table for time data.

Affects issues:
- #2362
This commit is contained in:
Aurora Lahtela 2022-05-20 19:31:46 +03:00
parent a97489ecd2
commit 52b8afe6cb
40 changed files with 879 additions and 336 deletions

View File

@ -25,6 +25,7 @@ import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.gathering.listeners.Status;
import com.djrapitops.plan.identification.ServerInfo;
@ -114,7 +115,9 @@ public class PlayerOnlineListener implements Listener {
boolean banned = result == PlayerLoginEvent.Result.KICK_BANNED;
String joinAddress = event.getHostname();
if (!joinAddress.isEmpty()) {
joinAddresses.put(playerUUID, joinAddress.substring(0, joinAddress.lastIndexOf(':')));
joinAddress = joinAddress.substring(0, joinAddress.lastIndexOf(':'));
joinAddresses.put(playerUUID, joinAddress);
dbSystem.getDatabase().executeTransaction(new StoreJoinAddressTransaction(joinAddress));
}
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> banned));
} catch (Exception e) {
@ -166,7 +169,7 @@ public class PlayerOnlineListener implements Listener {
BukkitAFKListener.afkTracker.performedAction(playerUUID, time);
String world = player.getWorld().getName();
String gm = Optional.ofNullable(player.getGameMode()).map(gameMode -> gameMode.name()).orElse("Unknown");
String gm = Optional.ofNullable(player.getGameMode()).map(Enum::name).orElse("Unknown");
Database database = dbSystem.getDatabase();
database.executeTransaction(new WorldNameStoreTransaction(serverUUID, world));
@ -192,8 +195,10 @@ public class PlayerOnlineListener implements Listener {
ActiveSession session = new ActiveSession(playerUUID, serverUUID, time, world, gm);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName()));
session.getExtraData().put(JoinAddress.class, new JoinAddress(getHostName));
sessionCache.cacheSession(playerUUID, session)
.ifPresent(previousSession -> database.executeTransaction(new SessionEndTransaction(previousSession)));
.map(SessionEndTransaction::new)
.ifPresent(database::executeTransaction);
database.executeTransaction(new NicknameStoreTransaction(
playerUUID, new Nickname(displayName, time, serverUUID),

View File

@ -48,7 +48,6 @@ public class PerServerContainer extends HashMap<ServerUUID, DataContainer> {
putToContainerOfServer(serverUUID, PerServerKeys.REGISTERED, userInfo.getRegistered());
putToContainerOfServer(serverUUID, PerServerKeys.BANNED, userInfo.isBanned());
putToContainerOfServer(serverUUID, PerServerKeys.OPERATOR, userInfo.isOperator());
putToContainerOfServer(serverUUID, PerServerKeys.JOIN_ADDRESS, userInfo.getJoinAddress());
}
public void putUserInfo(Collection<UserInfo> userInformation) {

View File

@ -27,6 +27,7 @@ import com.djrapitops.plan.delivery.rendering.html.Html;
import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs;
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie;
import com.djrapitops.plan.gathering.domain.*;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.settings.config.WorldAliasSettings;
import com.djrapitops.plan.utilities.analysis.Median;
@ -289,6 +290,8 @@ public class SessionsMutator {
WorldPie worldPie = graphs.pie().worldPie(session.getExtraData(WorldTimes.class).orElseGet(WorldTimes::new));
sessionMap.put("world_series", worldPie.getSlices());
sessionMap.put("gm_series", worldPie.toHighChartsDrillDownMaps());
sessionMap.put("join_address", session.getExtraData(JoinAddress.class)
.map(JoinAddress::getAddress).orElse("-"));
session.getExtraData(AveragePing.class).ifPresent(averagePing ->
sessionMap.put("avg_ping", formatters.decimals().apply(averagePing.getValue()) + " ms")

View File

@ -50,6 +50,7 @@ import com.djrapitops.plan.storage.database.queries.analysis.ActivityIndexQuerie
import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries;
import com.djrapitops.plan.storage.database.queries.analysis.PlayerCountQueries;
import com.djrapitops.plan.storage.database.queries.objects.*;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.utilities.java.Lists;
import com.djrapitops.plan.utilities.java.Maps;
import net.playeranalytics.plugin.scheduling.TimeAmount;
@ -123,7 +124,7 @@ public class GraphJSONCreator {
}
public Map<String, Object> optimizedPerformanceGraphJSON(ServerUUID serverUUID, URIQuery query) {
long after = getAfter(query); // TODO Implement if performance issues become apparent.
// long after = getAfter(query); // TODO Implement if performance issues become apparent.
long now = System.currentTimeMillis();
long twoMonthsAgo = now - TimeUnit.DAYS.toMillis(60);
@ -438,7 +439,7 @@ public class GraphJSONCreator {
public Map<String, Object> playerHostnamePieJSONAsMap() {
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(UserInfoQueries.joinAddresses());
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses());
translateUnknown(joinAddresses);
@ -450,7 +451,7 @@ public class GraphJSONCreator {
public Map<String, Object> playerHostnamePieJSONAsMap(ServerUUID serverUUID) {
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(UserInfoQueries.joinAddresses(serverUUID));
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses(serverUUID));
translateUnknown(joinAddresses);
@ -461,9 +462,9 @@ public class GraphJSONCreator {
}
public void translateUnknown(Map<String, Integer> joinAddresses) {
Integer unknown = joinAddresses.get("unknown");
Integer unknown = joinAddresses.get(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
if (unknown != null) {
joinAddresses.remove("unknown");
joinAddresses.remove(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
joinAddresses.put(locale.getString(GenericLang.UNKNOWN).toLowerCase(), unknown);
}
}

View File

@ -151,6 +151,7 @@ public class DBOpException extends IllegalStateException implements ExceptionWit
public boolean isUserIdConstraintViolation() {
return context != null
&& context.getRelated().contains(DBOpException.CONSTRAINT_VIOLATION)
&& getMessage().contains("user_id");
&& getCause() != null
&& getCause().getMessage().contains("user_id");
}
}

View File

@ -41,6 +41,9 @@ public class DataMap {
return Optional.ofNullable(ofType.cast(data.get(ofType.getName())));
}
public void remove(Class<?> ofType) {
data.remove(ofType.getName());
}
@Override
public boolean equals(Object o) {

View File

@ -17,7 +17,9 @@
package com.djrapitops.plan.gathering.domain;
import com.djrapitops.plan.delivery.domain.DateHolder;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;
@ -47,7 +49,7 @@ public class FinishedSession implements DateHolder {
this.start = start;
this.end = end;
this.afkTime = afkTime;
this.extraData = extraData;
this.extraData = extraData != null ? extraData : new DataMap();
}
public UUID getPlayerUUID() {
@ -113,50 +115,6 @@ public class FinishedSession implements DateHolder {
return getStart();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FinishedSession that = (FinishedSession) o;
return start == that.start && end == that.end &&
afkTime == that.afkTime &&
Objects.equals(playerUUID, that.playerUUID) &&
Objects.equals(serverUUID, that.serverUUID) &&
Objects.equals(getExtraData(WorldTimes.class), that.getExtraData(WorldTimes.class)) &&
Objects.equals(getExtraData(PlayerKills.class), that.getExtraData(PlayerKills.class)) &&
Objects.equals(getExtraData(MobKillCounter.class), that.getExtraData(MobKillCounter.class)) &&
Objects.equals(getExtraData(DeathCounter.class), that.getExtraData(DeathCounter.class));
}
@Override
public int hashCode() {
return Objects.hash(playerUUID, serverUUID, start, end, afkTime, extraData);
}
@Override
public String toString() {
return "FinishedSession{" +
"playerUUID=" + playerUUID +
", serverUUID=" + serverUUID +
", start=" + start +
", end=" + end +
", afkTime=" + afkTime +
", extraData=" + extraData +
'}';
}
public static class Id {
private final int value;
public Id(int value) {
this.value = value;
}
public int get() {
return value;
}
}
/**
* Deserialize csv format of the session.
*
@ -182,9 +140,43 @@ public class FinishedSession implements DateHolder {
extraData.put(PlayerKills.class, gson.fromJson(asArray[6], PlayerKills.class));
extraData.put(MobKillCounter.class, gson.fromJson(asArray[7], MobKillCounter.class));
extraData.put(DeathCounter.class, gson.fromJson(asArray[8], DeathCounter.class));
extraData.put(JoinAddress.class, new JoinAddress(asArray[9]));
return Optional.of(new FinishedSession(playerUUID, serverUUID, start, end, afkTime, extraData));
}
@Override
public int hashCode() {
return Objects.hash(playerUUID, serverUUID, start, end, afkTime, extraData);
}
@Override
public String toString() {
return "FinishedSession{" +
"playerUUID=" + playerUUID +
", serverUUID=" + serverUUID +
", start=" + start +
", end=" + end +
", afkTime=" + afkTime +
", extraData=" + extraData +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FinishedSession that = (FinishedSession) o;
return start == that.start && end == that.end &&
afkTime == that.afkTime &&
Objects.equals(playerUUID, that.playerUUID) &&
Objects.equals(serverUUID, that.serverUUID) &&
Objects.equals(getExtraData(WorldTimes.class), that.getExtraData(WorldTimes.class)) &&
Objects.equals(getExtraData(PlayerKills.class), that.getExtraData(PlayerKills.class)) &&
Objects.equals(getExtraData(MobKillCounter.class), that.getExtraData(MobKillCounter.class)) &&
Objects.equals(getExtraData(DeathCounter.class), that.getExtraData(DeathCounter.class)) &&
Objects.equals(getExtraData(JoinAddress.class), that.getExtraData(JoinAddress.class));
}
/**
* Serialize into csv format.
*
@ -199,6 +191,19 @@ public class FinishedSession implements DateHolder {
getExtraData(WorldTimes.class).orElseGet(WorldTimes::new).toJson() + ';' +
getExtraData(PlayerKills.class).orElseGet(PlayerKills::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();
}
public static class Id {
private final int value;
public Id(int value) {
this.value = value;
}
public int get() {
return value;
}
}
}

View File

@ -55,6 +55,10 @@ public class UserInfo {
return serverUUID;
}
/**
* @deprecated Join address is now stored in {@link FinishedSession#getExtraData()}, this method may become unreliable in the future.
*/
@Deprecated
public String getJoinAddress() {
return joinAddress;
}

View File

@ -16,21 +16,41 @@
*/
package com.djrapitops.plan.gathering.domain.event;
import java.util.Objects;
import java.util.function.Supplier;
public class JoinAddress {
private final String address;
private final Supplier<String> address;
public JoinAddress(String address) {
this(() -> address);
}
public JoinAddress(Supplier<String> address) {
this.address = address;
}
public String getAddress() {
return address;
return address.get();
}
@Override
public String toString() {
return "JoinAddress{" +
"address='" + address + '\'' +
"address='" + address.get() + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JoinAddress that = (JoinAddress) o;
return Objects.equals(getAddress(), that.getAddress());
}
@Override
public int hashCode() {
return Objects.hash(getAddress());
}
}

View File

@ -76,9 +76,7 @@ public class DataPipelineModule {
service.pull(ActiveSession.class, uuid)
.map(ActiveSession::getExtraData)
.flatMap(extra -> extra.get(PlayerKills.class)))
.registerSink(UUID.class, MobKill.class, (uuid, kill) -> {
service.pull(MobKillCounter.class, uuid).ifPresent(Counter::add);
})
.registerSink(UUID.class, MobKill.class, (uuid, kill) -> service.pull(MobKillCounter.class, uuid).ifPresent(Counter::add))
.registerSink(UUID.class, PlayerKill.class, (uuid, kill) -> {
service.pull(PlayerKills.class, kill.getKiller().getUuid()).ifPresent(playerKills -> playerKills.add(kill));
service.pull(DeathCounter.class, kill.getVictim().getUuid()).ifPresent(Counter::add);
@ -91,7 +89,7 @@ public class DataPipelineModule {
DataService.Pipeline playerLeaveToSession(SessionCache sessionCache) {
return service -> service
.registerOptionalMapper(UUID.class, PlayerLeave.class, FinishedSession.class, sessionCache::endSession)
.registerDatabaseSink(UUID.class, FinishedSession.class, SessionEndTransaction::new);
.registerDatabaseSink(UUID.class, FinishedSession.class, (playerUUID, session) -> new SessionEndTransaction(session));
}
}

View File

@ -225,7 +225,8 @@ public abstract class SQLDB extends AbstractDatabase {
new SessionsOptimizationPatch(),
new UserInfoHostnameAllowNullPatch(),
new RegisterDateMinimizationPatch(),
new UsersTableNameLengthPatch()
new UsersTableNameLengthPatch(),
new SessionJoinAddressPatch()
};
}

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.storage.database.queries;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.gathering.domain.*;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.*;
import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement;
@ -68,6 +69,8 @@ public class DataStoreQueries {
statement.setInt(5, session.getMobKillCount());
statement.setLong(6, session.getAfkTime());
statement.setString(7, session.getServerUUID().toString());
statement.setString(8, session.getExtraData(JoinAddress.class)
.map(JoinAddress::getAddress).orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP));
}
};
}

View File

@ -19,9 +19,12 @@ package com.djrapitops.plan.storage.database.queries;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.delivery.domain.World;
import com.djrapitops.plan.delivery.domain.auth.User;
import com.djrapitops.plan.exceptions.database.DBOpException;
import com.djrapitops.plan.gathering.domain.*;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.queries.objects.JoinAddressQueries;
import com.djrapitops.plan.storage.database.queries.objects.WorldTimesQueries;
import com.djrapitops.plan.storage.database.sql.tables.*;
import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement;
@ -285,6 +288,8 @@ public class LargeStoreQueries {
statement.setInt(5, session.getMobKillCount());
statement.setLong(6, session.getAfkTime());
statement.setString(7, session.getServerUUID().toString());
statement.setString(8, session.getExtraData(JoinAddress.class)
.map(JoinAddress::getAddress).orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP));
statement.addBatch();
}
}
@ -294,6 +299,8 @@ public class LargeStoreQueries {
public static Executable storeAllSessionsWithKillAndWorldData(Collection<FinishedSession> sessions) {
return connection -> {
Set<World> existingWorlds = WorldTimesQueries.fetchWorlds().executeWithConnection(connection);
List<String> existingJoinAddresses = JoinAddressQueries.allJoinAddresses().executeWithConnection(connection);
storeAllJoinAddresses(sessions, existingJoinAddresses).execute(connection);
storeAllWorldNames(sessions, existingWorlds).execute(connection);
storeAllSessionsWithoutKillOrWorldData(sessions).execute(connection);
storeSessionKillData(sessions).execute(connection);
@ -301,16 +308,41 @@ public class LargeStoreQueries {
};
}
private static Executable storeAllJoinAddresses(Collection<FinishedSession> sessions, List<String> existingJoinAddresses) {
return new ExecBatchStatement(JoinAddressTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) {
sessions.stream()
.map(FinishedSession::getExtraData)
.map(extraData -> extraData.get(JoinAddress.class))
.filter(Optional::isPresent)
.map(Optional::get)
.map(JoinAddress::getAddress)
.map(String::toLowerCase)
.filter(address -> !existingJoinAddresses.contains(address))
.distinct()
.forEach(address -> {
try {
statement.setString(1, address);
statement.addBatch();
} catch (SQLException e) {
throw DBOpException.forCause(JoinAddressTable.INSERT_STATEMENT, e);
}
});
}
};
}
private static Executable storeAllWorldNames(Collection<FinishedSession> sessions, Set<World> existingWorlds) {
Set<World> worlds = sessions.stream().flatMap(session -> {
ServerUUID serverUUID = session.getServerUUID();
return session.getExtraData(WorldTimes.class)
.map(WorldTimes::getWorldTimes)
.map(Map::keySet)
.orElseGet(Collections::emptySet)
.stream()
.map(worldName -> new World(worldName, serverUUID));
}).filter(world -> !existingWorlds.contains(world))
ServerUUID serverUUID = session.getServerUUID();
return session.getExtraData(WorldTimes.class)
.map(WorldTimes::getWorldTimes)
.map(Map::keySet)
.orElseGet(Collections::emptySet)
.stream()
.map(worldName -> new World(worldName, serverUUID));
}).filter(world -> !existingWorlds.contains(world))
.collect(Collectors.toSet());
if (worlds.isEmpty()) return Executable.empty();

View File

@ -23,6 +23,7 @@ import com.djrapitops.plan.delivery.domain.keys.Key;
import com.djrapitops.plan.delivery.domain.keys.PerServerKeys;
import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.gathering.domain.UserInfo;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.SQLDB;
import com.djrapitops.plan.storage.database.queries.PerServerAggregateQueries;
@ -64,6 +65,12 @@ public class PerServerContainerQuery implements Query<PerServerContainer> {
for (Map.Entry<ServerUUID, List<FinishedSession>> entry : sessions.entrySet()) {
ServerUUID serverUUID = entry.getKey();
List<FinishedSession> serverSessions = entry.getValue();
if (!serverSessions.isEmpty()) {
serverSessions.get(0).getExtraData().get(JoinAddress.class).map(JoinAddress::getAddress)
.ifPresent(address ->
perServerContainer.putToContainerOfServer(serverUUID, PerServerKeys.JOIN_ADDRESS, address)
);
}
DataContainer serverContainer = perServerContainer.getOrDefault(serverUUID, new SupplierDataContainer());
serverContainer.putRawData(PerServerKeys.SESSIONS, serverSessions);

View File

@ -18,7 +18,7 @@ package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries;
import com.djrapitops.plan.storage.database.queries.objects.JoinAddressQueries;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -46,11 +46,11 @@ public class JoinAddressFilter extends MultiOptionFilter {
}
private List<String> getSelectionOptions() {
return dbSystem.getDatabase().query(UserInfoQueries.uniqueJoinAddresses());
return dbSystem.getDatabase().query(JoinAddressQueries.uniqueJoinAddresses());
}
@Override
public Set<Integer> getMatchingUserIds(InputFilterDto query) {
return dbSystem.getDatabase().query(UserInfoQueries.userIdsOfPlayersWithJoinAddresses(getSelected(query)));
return dbSystem.getDatabase().query(JoinAddressQueries.userIdsOfPlayersWithJoinAddresses(getSelected(query)));
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.queries.objects;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.queries.QueryAllStatement;
import com.djrapitops.plan.storage.database.queries.QueryStatement;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.storage.database.sql.tables.ServerTable;
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
import org.apache.commons.text.TextStringBuilder;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
public class JoinAddressQueries {
private JoinAddressQueries() {
/* Static method class */
}
public static Query<Map<String, Integer>> latestJoinAddresses() {
String selectLatestJoinAddresses = SELECT +
"COUNT(1) as total," +
JoinAddressTable.JOIN_ADDRESS +
FROM + SessionsTable.TABLE_NAME + " a" +
LEFT_JOIN + SessionsTable.TABLE_NAME + " b on a." + SessionsTable.ID + "<b." + SessionsTable.ID +
AND + "a." + SessionsTable.USER_ID + "=b." + SessionsTable.USER_ID +
INNER_JOIN + JoinAddressTable.TABLE_NAME + " j on j." + JoinAddressTable.ID + "=a." + SessionsTable.JOIN_ADDRESS_ID +
WHERE + "b." + SessionsTable.ID + IS_NULL +
GROUP_BY + JoinAddressTable.JOIN_ADDRESS +
ORDER_BY + JoinAddressTable.JOIN_ADDRESS + " ASC";
return new QueryAllStatement<Map<String, Integer>>(selectLatestJoinAddresses, 100) {
@Override
public Map<String, Integer> processResults(ResultSet set) throws SQLException {
Map<String, Integer> joinAddresses = new TreeMap<>();
while (set.next()) {
joinAddresses.put(set.getString(JoinAddressTable.JOIN_ADDRESS), set.getInt("total"));
}
return joinAddresses;
}
};
}
public static Query<Map<String, Integer>> latestJoinAddresses(ServerUUID serverUUID) {
String selectLatestJoinAddresses = SELECT +
"COUNT(1) as total," +
JoinAddressTable.JOIN_ADDRESS +
FROM + SessionsTable.TABLE_NAME + " a" +
LEFT_JOIN + SessionsTable.TABLE_NAME + " b on a." + SessionsTable.ID + "<b." + SessionsTable.ID +
AND + "a." + SessionsTable.USER_ID + "=b." + SessionsTable.USER_ID +
AND + "a." + SessionsTable.SERVER_ID + "=b." + SessionsTable.SERVER_ID +
INNER_JOIN + JoinAddressTable.TABLE_NAME + " j on j." + JoinAddressTable.ID + "=a." + SessionsTable.JOIN_ADDRESS_ID +
WHERE + "b." + SessionsTable.ID + IS_NULL +
AND + "a." + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
GROUP_BY + JoinAddressTable.JOIN_ADDRESS +
ORDER_BY + JoinAddressTable.JOIN_ADDRESS + " ASC";
return new QueryStatement<Map<String, Integer>>(selectLatestJoinAddresses, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, serverUUID.toString());
}
@Override
public Map<String, Integer> processResults(ResultSet set) throws SQLException {
Map<String, Integer> joinAddresses = new TreeMap<>();
while (set.next()) {
joinAddresses.put(set.getString(JoinAddressTable.JOIN_ADDRESS), set.getInt("total"));
}
return joinAddresses;
}
};
}
public static QueryStatement<List<String>> allJoinAddresses() {
String sql = SELECT + JoinAddressTable.JOIN_ADDRESS +
FROM + JoinAddressTable.TABLE_NAME +
ORDER_BY + JoinAddressTable.JOIN_ADDRESS + " ASC";
return new QueryAllStatement<List<String>>(sql, 100) {
@Override
public List<String> processResults(ResultSet set) throws SQLException {
List<String> joinAddresses = new ArrayList<>();
while (set.next()) joinAddresses.add(set.getString(JoinAddressTable.JOIN_ADDRESS));
return joinAddresses;
}
};
}
public static Query<List<String>> uniqueJoinAddresses() {
return db -> {
List<String> addresses = db.query(allJoinAddresses());
if (!addresses.contains(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)) {
addresses.add(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
}
return addresses;
};
}
public static Query<Set<Integer>> userIdsOfPlayersWithJoinAddresses(List<String> joinAddresses) {
String sql = SELECT + DISTINCT + SessionsTable.USER_ID +
FROM + JoinAddressTable.TABLE_NAME + " j" +
INNER_JOIN + SessionsTable.TABLE_NAME + " s on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
WHERE + JoinAddressTable.JOIN_ADDRESS + " IN (" +
new TextStringBuilder().appendWithSeparators(joinAddresses.stream().map(item -> '?').iterator(), ",") +
')'; // Don't append addresses directly, SQL injection hazard
return new QueryStatement<Set<Integer>>(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (int i = 0; i < joinAddresses.size(); i++) {
statement.setString(i + 1, joinAddresses.get(i).toLowerCase());
}
}
@Override
public Set<Integer> processResults(ResultSet set) throws SQLException {
return UserInfoQueries.extractUserIds(set, SessionsTable.USER_ID);
}
};
}
}

View File

@ -22,6 +22,7 @@ import com.djrapitops.plan.delivery.domain.ServerIdentifier;
import com.djrapitops.plan.delivery.domain.ServerName;
import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator;
import com.djrapitops.plan.gathering.domain.*;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.queries.Query;
@ -70,6 +71,7 @@ public class SessionQueries {
WorldTimesTable.ADVENTURE + ',' +
WorldTimesTable.SPECTATOR + ',' +
WorldTable.NAME + ',' +
"j." + JoinAddressTable.JOIN_ADDRESS + " as join_address," +
KillsTable.KILLER_UUID + ',' +
KillsTable.VICTIM_UUID + ',' +
"v." + UsersTable.USER_NAME + " as victim_name, " +
@ -77,6 +79,7 @@ public class SessionQueries {
KillsTable.DATE + ',' +
KillsTable.WEAPON +
FROM + SessionsTable.TABLE_NAME + " s" +
INNER_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=s." + SessionsTable.USER_ID +
INNER_JOIN + ServerTable.TABLE_NAME + " server on server." + ServerTable.ID + "=s." + SessionsTable.SERVER_ID +
LEFT_JOIN + UserInfoTable.TABLE_NAME + " u_info on (u_info." + UserInfoTable.USER_ID + "=s." + SessionsTable.USER_ID + AND + "u_info." + UserInfoTable.SERVER_ID + "=s." + SessionsTable.SERVER_ID + ')' +
@ -114,7 +117,7 @@ public class SessionQueries {
String sql = SELECT_SESSIONS_STATEMENT +
WHERE + "s." + SessionsTable.USER_ID + "=" + UsersTable.SELECT_USER_ID +
ORDER_BY_SESSION_START_DESC;
return new QueryStatement<Map<ServerUUID, List<FinishedSession>>>(sql, 50000) {
return new QueryStatement<Map<ServerUUID, List<FinishedSession>>>(sql, 1000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, playerUUID.toString());
@ -159,6 +162,7 @@ public class SessionQueries {
extraData.put(FinishedSession.Id.class, new FinishedSession.Id(set.getInt(SessionsTable.ID)));
extraData.put(MobKillCounter.class, new MobKillCounter(set.getInt(SessionsTable.MOB_KILLS)));
extraData.put(DeathCounter.class, new DeathCounter(set.getInt(SessionsTable.DEATHS)));
extraData.put(JoinAddress.class, new JoinAddress(set.getString("join_address")));
Optional<WorldTimes> existingWorldTimes = extraData.get(WorldTimes.class);
Optional<PlayerKills> existingPlayerKills = extraData.get(PlayerKills.class);

View File

@ -158,82 +158,6 @@ public class UserInfoQueries {
};
}
public static Query<Map<String, Integer>> joinAddresses() {
String sql = SELECT +
"COUNT(1) as total," +
"LOWER(COALESCE(" + UserInfoTable.JOIN_ADDRESS + ", ?)) as address" +
FROM + '(' +
SELECT + DISTINCT +
UserInfoTable.USER_ID + ',' +
UserInfoTable.JOIN_ADDRESS +
FROM + UserInfoTable.TABLE_NAME +
") q1" +
GROUP_BY + "address" +
ORDER_BY + "address ASC";
return new QueryStatement<Map<String, Integer>>(sql, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, "Unknown");
}
@Override
public Map<String, Integer> processResults(ResultSet set) throws SQLException {
Map<String, Integer> joinAddresses = new TreeMap<>();
while (set.next()) {
joinAddresses.put(set.getString("address"), set.getInt("total"));
}
return joinAddresses;
}
};
}
public static Query<Map<String, Integer>> joinAddresses(ServerUUID serverUUID) {
String sql = SELECT +
"COUNT(1) as total," +
"LOWER(COALESCE(" + UserInfoTable.JOIN_ADDRESS + ", ?)) as address" +
FROM + UserInfoTable.TABLE_NAME +
WHERE + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
GROUP_BY + "address" +
ORDER_BY + "address ASC";
return new QueryStatement<Map<String, Integer>>(sql, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, "Unknown");
statement.setString(2, serverUUID.toString());
}
@Override
public Map<String, Integer> processResults(ResultSet set) throws SQLException {
Map<String, Integer> joinAddresses = new TreeMap<>();
while (set.next()) {
joinAddresses.put(set.getString("address"), set.getInt("total"));
}
return joinAddresses;
}
};
}
public static Query<List<String>> uniqueJoinAddresses() {
String sql = SELECT + DISTINCT + "LOWER(COALESCE(" + UserInfoTable.JOIN_ADDRESS + ", ?)) as address" +
FROM + UserInfoTable.TABLE_NAME +
ORDER_BY + "address ASC";
return new QueryStatement<List<String>>(sql, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, "unknown");
}
@Override
public List<String> processResults(ResultSet set) throws SQLException {
List<String> joinAddresses = new ArrayList<>();
while (set.next()) joinAddresses.add(set.getString("address"));
return joinAddresses;
}
};
}
public static Query<Set<Integer>> userIdsOfOperators() {
return getUserIdsForBooleanGroup(UserInfoTable.OP, true);
}
@ -257,9 +181,13 @@ public class UserInfoQueries {
}
public static Set<Integer> extractUserIds(ResultSet set) throws SQLException {
return extractUserIds(set, UsersTable.ID);
}
public static Set<Integer> extractUserIds(ResultSet set, String column) throws SQLException {
Set<Integer> userIds = new HashSet<>();
while (set.next()) {
userIds.add(set.getInt(UsersTable.ID));
userIds.add(set.getInt(column));
}
return userIds;
}
@ -276,35 +204,6 @@ public class UserInfoQueries {
return getUserIdsForBooleanGroup(UserInfoTable.BANNED, false);
}
public static Query<Set<Integer>> userIdsOfPlayersWithJoinAddresses(List<String> joinAddresses) {
String selectLowercaseJoinAddresses = SELECT +
"u." + UsersTable.ID + ',' +
"LOWER(COALESCE(" + UserInfoTable.JOIN_ADDRESS + ", ?)) as address" +
FROM + UserInfoTable.TABLE_NAME +
INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + '=' + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.USER_ID;
String sql = SELECT + DISTINCT + UsersTable.ID +
FROM + '(' + selectLowercaseJoinAddresses + ") q1" +
WHERE + "address IN (" +
new TextStringBuilder().appendWithSeparators(joinAddresses.stream().map(item -> '?').iterator(), ",") +
')'; // Don't append addresses directly, SQL injection hazard
return new QueryStatement<Set<Integer>>(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, "unknown");
for (int i = 1; i <= joinAddresses.size(); i++) {
String address = joinAddresses.get(i - 1);
statement.setString(i + 1, address);
}
}
@Override
public Set<Integer> processResults(ResultSet set) throws SQLException {
return extractUserIds(set);
}
};
}
public static Query<Set<Integer>> userIdsOfRegisteredBetween(long after, long before, List<ServerUUID> serverUUIDs) {
String selectServerIds = SELECT + ServerTable.ID +
FROM + ServerTable.TABLE_NAME +

View File

@ -0,0 +1,44 @@
/*
* 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.sql.tables;
import com.djrapitops.plan.storage.database.DBType;
import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder;
import com.djrapitops.plan.storage.database.sql.building.Sql;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
public class JoinAddressTable {
public static final String TABLE_NAME = "plan_join_address";
public static final String ID = "id";
public static final String JOIN_ADDRESS = "join_address";
public static final String SELECT_ID = '(' + SELECT + ID + FROM + TABLE_NAME + WHERE + JOIN_ADDRESS + "=LOWER(?))";
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME +
" (" + JOIN_ADDRESS + ") VALUES (LOWER(?))";
public static final String DEFAULT_VALUE_FOR_LOOKUP = "unknown";
private JoinAddressTable() {}
public static String createTableSQL(DBType dbType) {
return CreateTableBuilder.create(TABLE_NAME, dbType)
.column(ID, Sql.INT).primaryKey()
.column(JOIN_ADDRESS, Sql.varchar(255)).unique()
.toString();
}
}

View File

@ -32,6 +32,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
* {@link Version10Patch}
* {@link SessionAFKTimePatch}
* {@link SessionsOptimizationPatch}
* {@link com.djrapitops.plan.storage.database.transactions.patches.SessionJoinAddressPatch}
*
* @author AuroraLS3
*/
@ -47,6 +48,7 @@ public class SessionsTable {
public static final String MOB_KILLS = "mob_kills";
public static final String DEATHS = "deaths";
public static final String AFK_TIME = "afk_time";
public static final String JOIN_ADDRESS_ID = "join_address_id";
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " ("
+ USER_ID + ','
@ -55,8 +57,9 @@ public class SessionsTable {
+ DEATHS + ','
+ MOB_KILLS + ','
+ AFK_TIME + ','
+ SERVER_ID
+ ") VALUES (" + UsersTable.SELECT_USER_ID + ", ?, ?, ?, ?, ?, " + ServerTable.SELECT_SERVER_ID + ")";
+ SERVER_ID + ','
+ JOIN_ADDRESS_ID
+ ") VALUES (" + UsersTable.SELECT_USER_ID + ", ?, ?, ?, ?, ?, " + ServerTable.SELECT_SERVER_ID + ", " + JoinAddressTable.SELECT_ID + ")";
public static final String SELECT_SESSION_ID_STATEMENT = "(SELECT " + TABLE_NAME + '.' + ID + FROM + TABLE_NAME +
WHERE + TABLE_NAME + '.' + USER_ID + "=" + UsersTable.SELECT_USER_ID +
@ -78,6 +81,7 @@ public class SessionsTable {
.column(MOB_KILLS, Sql.INT).notNull()
.column(DEATHS, Sql.INT).notNull()
.column(AFK_TIME, Sql.LONG).notNull()
.column(JOIN_ADDRESS_ID, INT).notNull().defaultValue("1") // References JoinAddressTable.ID, but no foreign key to allow null values.
.foreignKey(USER_ID, UsersTable.TABLE_NAME, UsersTable.ID)
.foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID)
.toString();

View File

@ -42,6 +42,10 @@ public class UserInfoTable {
public static final String REGISTERED = "registered";
public static final String OP = "opped";
public static final String BANNED = "banned";
/**
* @deprecated Join address is now stored in {@link JoinAddressTable}, this column may become unreliable in the future.
*/
@Deprecated
public static final String JOIN_ADDRESS = "join_address";
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" +

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.storage.database.transactions.commands;
import com.djrapitops.plan.storage.database.sql.tables.*;
import com.djrapitops.plan.storage.database.transactions.events.StoreJoinAddressTransaction;
import com.djrapitops.plan.storage.database.transactions.patches.Patch;
/**
@ -39,6 +40,7 @@ public class RemoveEverythingTransaction extends Patch {
clearTable(KillsTable.TABLE_NAME);
clearTable(WorldTimesTable.TABLE_NAME);
clearTable(SessionsTable.TABLE_NAME);
clearTable(JoinAddressTable.TABLE_NAME);
clearTable(WorldTable.TABLE_NAME);
clearTable(PingTable.TABLE_NAME);
clearTable(UserInfoTable.TABLE_NAME);
@ -57,6 +59,8 @@ public class RemoveEverythingTransaction extends Patch {
clearTable(ExtensionTabTable.TABLE_NAME);
clearTable(ExtensionPluginTable.TABLE_NAME);
clearTable(ExtensionIconTable.TABLE_NAME);
executeOther(new StoreJoinAddressTransaction(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP));
}
private void clearTable(String tableName) {

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.storage.database.transactions.events;
import com.djrapitops.plan.exceptions.database.DBOpException;
import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.storage.database.queries.DataStoreQueries;
import com.djrapitops.plan.storage.database.transactions.Transaction;
@ -32,10 +33,6 @@ public class SessionEndTransaction extends Transaction {
private final FinishedSession session;
public SessionEndTransaction(UUID playerUUID, FinishedSession session) {
this(session);
}
public SessionEndTransaction(FinishedSession session) {
this.session = session;
}
@ -43,19 +40,36 @@ public class SessionEndTransaction extends Transaction {
@Override
protected void performOperations() {
try {
execute(DataStoreQueries.storeSession(session));
storeSession();
} catch (DBOpException failed) {
if (failed.isUserIdConstraintViolation()) {
retry();
retry(failed);
} else {
throw failed;
}
}
}
private void retry() {
UUID playerUUID = session.getPlayerUUID();
executeOther(new PlayerRegisterTransaction(playerUUID, System::currentTimeMillis, playerUUID.toString()));
private void storeSession() {
storeJoinAddressIfPresent();
execute(DataStoreQueries.storeSession(session));
}
private void storeJoinAddressIfPresent() {
session.getExtraData(JoinAddress.class)
.map(JoinAddress::getAddress)
.map(StoreJoinAddressTransaction::new)
.ifPresent(this::executeOther);
}
private void retry(DBOpException failed) {
try {
UUID playerUUID = session.getPlayerUUID();
executeOther(new PlayerRegisterTransaction(playerUUID, System::currentTimeMillis, playerUUID.toString()));
storeSession();
} catch (DBOpException anotherFail) {
anotherFail.addSuppressed(failed);
throw anotherFail;
}
}
}

View File

@ -0,0 +1,71 @@
/*
* 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.container.CachingSupplier;
import com.djrapitops.plan.storage.database.queries.HasMoreThanZeroQueryStatement;
import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.function.Supplier;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
public class StoreJoinAddressTransaction extends Transaction {
private final Supplier<String> joinAddress;
public StoreJoinAddressTransaction(String joinAddress) {
this(() -> joinAddress);
}
public StoreJoinAddressTransaction(Supplier<String> joinAddress) {
this.joinAddress = new CachingSupplier<>(joinAddress);
}
@Override
protected boolean shouldBeExecuted() {
return !query(hasAddressAlready());
}
private Query<Boolean> hasAddressAlready() {
String sql = SELECT + "COUNT(*) as c" +
FROM + JoinAddressTable.TABLE_NAME +
WHERE + JoinAddressTable.JOIN_ADDRESS + "=?";
return new HasMoreThanZeroQueryStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
String address = joinAddress.get();
statement.setString(1, address.toLowerCase());
}
};
}
@Override
protected void performOperations() {
execute(new ExecStatement(JoinAddressTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, joinAddress.get().toLowerCase());
}
});
}
}

View File

@ -63,6 +63,9 @@ public class CreateIndexTransaction extends Transaction {
createIndex(TPSTable.TABLE_NAME, "plan_tps_date_index",
TPSTable.DATE
);
createIndex(SessionsTable.TABLE_NAME, "plan_session_join_address_index",
SessionsTable.JOIN_ADDRESS_ID);
}
private void createIndex(String tableName, String indexName, String... indexedColumns) {

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.storage.database.transactions.init;
import com.djrapitops.plan.storage.database.sql.tables.*;
import com.djrapitops.plan.storage.database.transactions.events.StoreJoinAddressTransaction;
/**
* Transaction that creates the table schema of Plan database.
@ -36,6 +37,8 @@ public class CreateTablesTransaction extends OperationCriticalTransaction {
execute(UserInfoTable.createTableSQL(dbType));
execute(GeoInfoTable.createTableSQL(dbType));
execute(NicknamesTable.createTableSQL(dbType));
execute(JoinAddressTable.createTableSQL(dbType));
executeOther(new StoreJoinAddressTransaction(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP));
execute(SessionsTable.createTableSQL(dbType));
execute(KillsTable.createTableSQL(dbType));
execute(PingTable.createTableSQL(dbType));

View File

@ -0,0 +1,149 @@
/*
* 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.patches;
import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.queries.QueryAllStatement;
import com.djrapitops.plan.storage.database.queries.QueryStatement;
import com.djrapitops.plan.storage.database.sql.building.Sql;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable;
import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
/**
* Adds join_address_id to plan_sessions, and populates latest session rows with value from plan_user_info.
*
* @author AuroraLS3
*/
public class SessionJoinAddressPatch extends Patch {
public static Query<List<String>> uniqueJoinAddressesOld() {
String sql = SELECT + DISTINCT + "LOWER(COALESCE(" + UserInfoTable.JOIN_ADDRESS + ", ?)) as address" +
FROM + UserInfoTable.TABLE_NAME +
ORDER_BY + "address ASC";
return new QueryStatement<List<String>>(sql, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
}
@Override
public List<String> processResults(ResultSet set) throws SQLException {
List<String> joinAddresses = new ArrayList<>();
while (set.next()) joinAddresses.add(set.getString("address"));
return joinAddresses;
}
};
}
@Override
public boolean hasBeenApplied() {
return hasColumn(SessionsTable.TABLE_NAME, SessionsTable.JOIN_ADDRESS_ID);
}
@Override
protected void applyPatch() {
Integer defaultAddressId = getDefaultAddressId();
addColumn(SessionsTable.TABLE_NAME, SessionsTable.JOIN_ADDRESS_ID + ' ' + Sql.INT + " DEFAULT " + defaultAddressId);
populateAddresses();
populateLatestSessions();
}
private Integer getDefaultAddressId() {
return query(new QueryStatement<Integer>(SELECT + ID +
FROM + JoinAddressTable.TABLE_NAME +
WHERE + JoinAddressTable.JOIN_ADDRESS + "=LOWER(?)") {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
}
@Override
public Integer processResults(ResultSet set) throws SQLException {
return set.next() ? set.getInt(JoinAddressTable.ID) : 1;
}
});
}
private void populateAddresses() {
List<String> joinAddresses = query(uniqueJoinAddressesOld());
joinAddresses.remove(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
execute(new ExecBatchStatement(JoinAddressTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (String joinAddress : joinAddresses) {
statement.setString(1, joinAddress.toLowerCase());
statement.addBatch();
}
}
});
}
private void populateLatestSessions() {
String sql = SELECT +
"MAX(s." + SessionsTable.ID + ") as session_id," +
"j." + JoinAddressTable.ID + " as join_address_id" +
FROM + UserInfoTable.TABLE_NAME + " u" +
INNER_JOIN + SessionsTable.TABLE_NAME + " s on s." + SessionsTable.USER_ID + "=u." + UserInfoTable.USER_ID +
AND + "s." + SessionsTable.SERVER_ID + "=u." + UserInfoTable.SERVER_ID +
INNER_JOIN + JoinAddressTable.TABLE_NAME + " j on j." + JoinAddressTable.JOIN_ADDRESS + "=u." + UserInfoTable.JOIN_ADDRESS +
GROUP_BY + "u." + UserInfoTable.USER_ID + ",u." + UserInfoTable.SERVER_ID;
Map<Integer, Integer> joinAddressIdsBySessionId = query(new QueryAllStatement<Map<Integer, Integer>>(sql) {
@Override
public Map<Integer, Integer> processResults(ResultSet set) throws SQLException {
Map<Integer, Integer> joinAddressBySessionId = new TreeMap<>();
while (set.next()) {
joinAddressBySessionId.put(
set.getInt("session_id"),
set.getInt("join_address_id")
);
}
return joinAddressBySessionId;
}
});
String updateSql = "UPDATE " + SessionsTable.TABLE_NAME + " SET " + SessionsTable.JOIN_ADDRESS_ID + "=?" +
WHERE + SessionsTable.ID + "=?";
execute(new ExecBatchStatement(updateSql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (Map.Entry<Integer, Integer> sessionIdAndJoinAddressId : joinAddressIdsBySessionId.entrySet()) {
Integer sessionId = sessionIdAndJoinAddressId.getKey();
Integer joinAddressId = sessionIdAndJoinAddressId.getValue();
statement.setInt(1, joinAddressId);
statement.setInt(2, sessionId);
statement.addBatch();
}
}
});
}
}

View File

@ -30,7 +30,10 @@ import com.djrapitops.plan.query.QuerySvc;
import com.djrapitops.plan.settings.config.Config;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.storage.database.queries.*;
import com.djrapitops.plan.storage.database.queries.PlayerFetchQueries;
import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.queries.QueryAllStatement;
import com.djrapitops.plan.storage.database.queries.ServerAggregateQueries;
import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries;
import com.djrapitops.plan.storage.database.queries.objects.*;
import com.djrapitops.plan.storage.database.queries.objects.playertable.NetworkTablePlayersQuery;
@ -101,7 +104,7 @@ public interface DatabaseTest extends DatabaseTestPreparer {
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
execute(DataStoreQueries.storeSession(session));
db().executeTransaction(new SessionEndTransaction(session));
db().executeTransaction(new NicknameStoreTransaction(playerUUID, new Nickname("TestNick", RandomData.randomTime(), serverUUID()), (uuid, name) -> false /* Not cached */));
db().executeTransaction(new GeoInfoStoreTransaction(playerUUID, new GeoInfo("TestLoc", RandomData.randomTime())));
@ -131,7 +134,7 @@ public interface DatabaseTest extends DatabaseTestPreparer {
long sessionStart = System.currentTimeMillis();
ActiveSession session = new ActiveSession(playerUUID, serverUUID(), sessionStart, worlds[0], "SURVIVAL");
execute(DataStoreQueries.storeSession(session.toFinishedSession(sessionStart + 22345L)));
db().executeTransaction(new SessionEndTransaction(session.toFinishedSession(sessionStart + 22345L)));
TestPluginLogger logger = new TestPluginLogger();
new DBCleanTask(
@ -154,7 +157,7 @@ public interface DatabaseTest extends DatabaseTestPreparer {
saveUserTwo();
saveTwoWorlds();
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
execute(DataStoreQueries.storeSession(session));
db().executeTransaction(new SessionEndTransaction(session));
db().executeTransaction(new NicknameStoreTransaction(playerUUID, RandomData.randomNickname(serverUUID()), (uuid, name) -> false /* Not cached */));
saveGeoInfo(playerUUID, new GeoInfo("TestLoc", RandomData.randomTime()));
assertTrue(db().query(PlayerFetchQueries.isPlayerRegistered(playerUUID)));

View File

@ -28,6 +28,7 @@ import com.djrapitops.plan.storage.database.queries.objects.playertable.ServerTa
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
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 org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
@ -59,7 +60,7 @@ public interface ActivityIndexQueriesTest extends DatabaseTestPreparer {
}
for (FinishedSession session : RandomData.randomSessions(serverUUID(), worlds, playerUUID, player2UUID)) {
if (save.test(session)) execute(DataStoreQueries.storeSession(session));
if (save.test(session)) db().executeTransaction(new SessionEndTransaction(session));
}
}

View File

@ -52,7 +52,7 @@ public interface DatabaseBackupTest extends DatabaseTestPreparer {
TestConstants.PLAYER_TWO_NAME, serverUUID(), TestConstants.GET_PLAYER_HOSTNAME));
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
execute(DataStoreQueries.storeSession(session));
db().executeTransaction(new SessionEndTransaction(session));
db().executeTransaction(
new NicknameStoreTransaction(playerUUID, RandomData.randomNickname(serverUUID()), (uuid, name) -> false /* Not cached */)

View File

@ -38,6 +38,7 @@ import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DatabaseTestPreparer;
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.SessionEndTransaction;
import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -121,7 +122,7 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
// Store a session to check against issue https://github.com/plan-player-analytics/Plan/issues/1039
ActiveSession session = new ActiveSession(playerUUID, serverUUID(), 32345L, worlds[0], "SURVIVAL");
session.getExtraData().put(WorldTimes.class, RandomData.randomWorldTimes(worlds));
execute(DataStoreQueries.storeSession(session.toFinishedSession(42345L)));
db().executeTransaction(new SessionEndTransaction(session.toFinishedSession(42345L)));
Map<UUID, ExtensionTabData> result = db().query(new ExtensionServerTableDataQuery(serverUUID(), 50));
assertEquals(1, result.size());

View File

@ -0,0 +1,210 @@
/*
* 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.queries;
import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.storage.database.DatabaseTestPreparer;
import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries;
import com.djrapitops.plan.storage.database.queries.objects.JoinAddressQueries;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
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.SessionEndTransaction;
import com.djrapitops.plan.storage.database.transactions.events.StoreJoinAddressTransaction;
import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction;
import org.junit.jupiter.api.Test;
import utilities.RandomData;
import utilities.TestConstants;
import utilities.TestData;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
public interface JoinAddressQueriesTest extends DatabaseTestPreparer {
@Test
default void removeEverythingRemovesJoinAddresses() {
String joinAddress = TestConstants.GET_PLAYER_HOSTNAME.get();
executeTransactions(new StoreJoinAddressTransaction(joinAddress));
List<String> expected = List.of(joinAddress, JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
List<String> result = db().query(JoinAddressQueries.uniqueJoinAddresses());
assertEquals(expected, result);
executeTransactions(new RemoveEverythingTransaction());
expected = List.of(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
result = db().query(JoinAddressQueries.uniqueJoinAddresses());
assertEquals(expected, result);
}
@Test
default void unknownJoinAddressIsStoredInDatabaseDuringCreation() {
List<String> expected = List.of(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
List<String> result = db().query(JoinAddressQueries.allJoinAddresses());
assertEquals(expected, result);
}
@Test
default void joinAddressCanBeUnknown() {
db().executeTransaction(new WorldNameStoreTransaction(serverUUID(), worlds[0]));
db().executeTransaction(new WorldNameStoreTransaction(serverUUID(), worlds[1]));
db().executeTransaction(new PlayerRegisterTransaction(playerUUID, System::currentTimeMillis, TestConstants.PLAYER_ONE_NAME));
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
session.getExtraData().remove(JoinAddress.class);
db().executeTransaction(new SessionEndTransaction(session));
Set<Integer> expected = Set.of(db().query(BaseUserQueries.fetchUserId(playerUUID)));
Set<Integer> result = db().query(JoinAddressQueries.userIdsOfPlayersWithJoinAddresses(List.of(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)));
assertEquals(expected, result);
Map<String, Integer> expectedAddressCounts = Map.of(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP, 1);
Map<String, Integer> resultAddressCounts = db().query(JoinAddressQueries.latestJoinAddresses());
assertEquals(expectedAddressCounts, resultAddressCounts);
}
@Test
default void latestJoinAddressIsUpdatedUponSecondSession() {
joinAddressCanBeUnknown();
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
String expectedAddress = TestConstants.GET_PLAYER_HOSTNAME.get();
session.getExtraData().put(JoinAddress.class, new JoinAddress(expectedAddress));
db().executeTransaction(new SessionEndTransaction(session));
Map<String, Integer> expected = Map.of(expectedAddress, 1);
Map<String, Integer> result = db().query(JoinAddressQueries.latestJoinAddresses());
assertEquals(expected, result);
}
@Test
default void joinAddressUpdateIsUniquePerServer() {
joinAddressCanBeUnknown();
db().executeTransaction(TestData.storeServers());
db().executeTransaction(new WorldNameStoreTransaction(TestConstants.SERVER_TWO_UUID, worlds[0]));
db().executeTransaction(new WorldNameStoreTransaction(TestConstants.SERVER_TWO_UUID, worlds[1]));
FinishedSession session = RandomData.randomSession(TestConstants.SERVER_TWO_UUID, worlds, playerUUID, player2UUID);
String expectedAddress = TestConstants.GET_PLAYER_HOSTNAME.get();
session.getExtraData().put(JoinAddress.class, new JoinAddress(expectedAddress));
db().executeTransaction(new SessionEndTransaction(session));
Map<String, Integer> expected1 = Map.of(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP, 1);
Map<String, Integer> result1 = db().query(JoinAddressQueries.latestJoinAddresses(serverUUID()));
assertEquals(expected1, result1);
Map<String, Integer> expected2 = Map.of(expectedAddress, 1);
Map<String, Integer> result2 = db().query(JoinAddressQueries.latestJoinAddresses(TestConstants.SERVER_TWO_UUID));
assertEquals(expected2, result2);
}
@Test
default void joinAddressQueryHasNoNullValues() {
joinAddressCanBeUnknown();
Map<String, Integer> expected = Collections.singletonMap(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP, 1);
Map<String, Integer> result = db().query(JoinAddressQueries.latestJoinAddresses());
assertEquals(expected, result);
}
@Test
default void serverJoinAddressQueryHasNoNullValues() {
joinAddressCanBeUnknown();
Map<String, Integer> expected = Collections.singletonMap(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP, 1);
Map<String, Integer> result = db().query(JoinAddressQueries.latestJoinAddresses(serverUUID()));
assertEquals(expected, result);
}
@Test
default void joinAddressQueryHasDistinctPlayers() {
joinAddressCanBeUnknown();
db().executeTransaction(TestData.storeServers());
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, player2UUID, playerUUID);
String expectedAddress = TestConstants.GET_PLAYER_HOSTNAME.get();
session.getExtraData().put(JoinAddress.class, new JoinAddress(expectedAddress));
db().executeTransaction(new SessionEndTransaction(session));
Map<String, Integer> expected = Map.of(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP, 1, expectedAddress, 1);
Map<String, Integer> result = db().query(JoinAddressQueries.latestJoinAddresses());
assertEquals(expected, result);
}
@Test
default void joinAddressFilterOptionsAreFetched() {
executeTransactions(
new StoreJoinAddressTransaction(TestConstants.GET_PLAYER_HOSTNAME.get())
);
List<String> expected = List.of(TestConstants.GET_PLAYER_HOSTNAME.get().toLowerCase(), JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
List<String> result = db().query(JoinAddressQueries.uniqueJoinAddresses());
assertEquals(expected, result);
}
@Test
default void joinAddressFilterOptionsAreFetchedWhenThereAreMultiple() {
executeTransactions(
new StoreJoinAddressTransaction(TestConstants.GET_PLAYER_HOSTNAME.get()),
new StoreJoinAddressTransaction(TestConstants.GET_PLAYER_HOSTNAME.get() + "_a"),
new StoreJoinAddressTransaction(TestConstants.GET_PLAYER_HOSTNAME.get() + "_b")
);
List<String> expected = Arrays.asList(
TestConstants.GET_PLAYER_HOSTNAME.get().toLowerCase(),
TestConstants.GET_PLAYER_HOSTNAME.get().toLowerCase() + "_a",
TestConstants.GET_PLAYER_HOSTNAME.get().toLowerCase() + "_b",
JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP
);
List<String> result = db().query(JoinAddressQueries.uniqueJoinAddresses());
assertEquals(expected, result);
}
@Test
default void joinAddressFilterUUIDsAreFetched() {
latestJoinAddressIsUpdatedUponSecondSession();
Set<Integer> expected = Set.of(db().query(BaseUserQueries.fetchUserId(playerUUID)));
Set<Integer> result = db().query(JoinAddressQueries.userIdsOfPlayersWithJoinAddresses(
List.of(TestConstants.GET_PLAYER_HOSTNAME.get().toLowerCase()))
);
assertEquals(expected, result);
}
@Test
default void joinAddressFilterUUIDsAreFetchedWhenUnknown() {
joinAddressCanBeUnknown();
Set<Integer> expected = Set.of(db().query(BaseUserQueries.fetchUserId(playerUUID)));
Set<Integer> result = db().query(JoinAddressQueries.userIdsOfPlayersWithJoinAddresses(
List.of(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP))
);
assertEquals(expected, result);
}
}

View File

@ -31,6 +31,7 @@ public interface QueriesTestAggregate extends
TPSQueriesTest,
UserInfoQueriesTest,
WebUserQueriesTest,
FilterQueryTest {
FilterQueryTest,
JoinAddressQueriesTest {
/* Collects all query tests together so its easier to implement database tests */
}

View File

@ -141,7 +141,7 @@ public interface SessionQueriesTest extends DatabaseTestPreparer {
long expectedLength = session.getLength();
long sessionEnd = session.getEnd();
execute(DataStoreQueries.storeSession(session));
db().executeTransaction(new SessionEndTransaction(session));
forcePersistenceCheck();
@ -170,7 +170,7 @@ public interface SessionQueriesTest extends DatabaseTestPreparer {
default void sessionsAreStoredWithAllData() {
prepareForSessionSave();
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
execute(DataStoreQueries.storeSession(session));
db().executeTransaction(new SessionEndTransaction(session));
forcePersistenceCheck();
@ -187,7 +187,7 @@ public interface SessionQueriesTest extends DatabaseTestPreparer {
default void mostRecentSessionsCanBeQueried() {
prepareForSessionSave();
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
execute(DataStoreQueries.storeSession(session));
db().executeTransaction(new SessionEndTransaction(session));
List<FinishedSession> expected = Collections.singletonList(session);
List<FinishedSession> result = db().query(SessionQueries.fetchLatestSessionsOfServer(serverUUID(), 1));
@ -294,7 +294,7 @@ public interface SessionQueriesTest extends DatabaseTestPreparer {
prepareForSessionSave();
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
execute(DataStoreQueries.storeSession(session));
db().executeTransaction(new SessionEndTransaction(session));
forcePersistenceCheck();
@ -310,7 +310,7 @@ public interface SessionQueriesTest extends DatabaseTestPreparer {
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
List<PlayerKill> expected = session.getExtraData(PlayerKills.class).map(PlayerKills::asList)
.orElseThrow(AssertionError::new);
execute(DataStoreQueries.storeSession(session));
db().executeTransaction(new SessionEndTransaction(session));
forcePersistenceCheck();
@ -330,7 +330,7 @@ public interface SessionQueriesTest extends DatabaseTestPreparer {
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
WorldTimes expected = session.getExtraData(WorldTimes.class).orElseThrow(AssertionError::new);
execute(DataStoreQueries.storeSession(session));
db().executeTransaction(new SessionEndTransaction(session));
// Fetch the session
Map<ServerUUID, List<FinishedSession>> sessions = db().query(SessionQueries.fetchSessionsOfPlayer(playerUUID));
@ -377,8 +377,8 @@ public interface SessionQueriesTest extends DatabaseTestPreparer {
prepareForSessionSave();
List<FinishedSession> player1Sessions = RandomData.randomSessions(serverUUID(), worlds, playerUUID, player2UUID);
List<FinishedSession> player2Sessions = RandomData.randomSessions(serverUUID(), worlds, player2UUID, playerUUID);
player1Sessions.forEach(session -> execute(DataStoreQueries.storeSession(session)));
player2Sessions.forEach(session -> execute(DataStoreQueries.storeSession(session)));
player1Sessions.forEach(session -> db().executeTransaction(new SessionEndTransaction(session)));
player2Sessions.forEach(session -> db().executeTransaction(new SessionEndTransaction(session)));
long playtimeThreshold = RandomData.randomLong(TimeUnit.HOURS.toMillis(1L), TimeUnit.DAYS.toMillis(2L));
@ -397,8 +397,8 @@ public interface SessionQueriesTest extends DatabaseTestPreparer {
prepareForSessionSave();
List<FinishedSession> player1Sessions = RandomData.randomSessions(serverUUID(), worlds, playerUUID, player2UUID);
List<FinishedSession> player2Sessions = RandomData.randomSessions(serverUUID(), worlds, player2UUID, playerUUID);
player1Sessions.forEach(session -> execute(DataStoreQueries.storeSession(session)));
player2Sessions.forEach(session -> execute(DataStoreQueries.storeSession(session)));
player1Sessions.forEach(session -> db().executeTransaction(new SessionEndTransaction(session)));
player2Sessions.forEach(session -> db().executeTransaction(new SessionEndTransaction(session)));
long time = System.currentTimeMillis();
long playtimeThreshold = RandomData.randomLong(TimeUnit.HOURS.toMillis(1L), TimeUnit.DAYS.toMillis(2L));
@ -433,14 +433,14 @@ public interface SessionQueriesTest extends DatabaseTestPreparer {
default void serverPreferencePieValuesAreCorrect() {
prepareForSessionSave();
List<FinishedSession> server1Sessions = RandomData.randomSessions(serverUUID(), worlds, playerUUID, player2UUID);
server1Sessions.forEach(session -> execute(DataStoreQueries.storeSession(session)));
server1Sessions.forEach(session -> db().executeTransaction(new SessionEndTransaction(session)));
ServerUUID serverTwoUuid = TestConstants.SERVER_TWO_UUID;
executeTransactions(new StoreServerInformationTransaction(new Server(serverTwoUuid, TestConstants.SERVER_TWO_NAME, "", TestConstants.VERSION)));
db().executeTransaction(new WorldNameStoreTransaction(serverTwoUuid, worlds[0]));
db().executeTransaction(new WorldNameStoreTransaction(serverTwoUuid, worlds[1]));
List<FinishedSession> server2Sessions = RandomData.randomSessions(serverTwoUuid, worlds, playerUUID, player2UUID);
server2Sessions.forEach(session -> execute(DataStoreQueries.storeSession(session)));
server2Sessions.forEach(session -> db().executeTransaction(new SessionEndTransaction(session)));
Map<String, Long> expected = Maps.builder(String.class, Long.class)
.put(TestConstants.SERVER_NAME, new SessionsMutator(server1Sessions).toPlaytime())

View File

@ -18,12 +18,10 @@ package com.djrapitops.plan.storage.database.queries;
import com.djrapitops.plan.gathering.domain.BaseUser;
import com.djrapitops.plan.gathering.domain.UserInfo;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.storage.database.DatabaseTestPreparer;
import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries;
import com.djrapitops.plan.storage.database.queries.objects.UserIdentifierQueries;
import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries;
import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
import com.djrapitops.plan.storage.database.transactions.events.*;
@ -51,45 +49,6 @@ public interface UserInfoQueriesTest extends DatabaseTestPreparer {
assertEquals(expected, userInfo);
}
@Test
default void joinAddressCanBeNull() {
assertFalse(db().query(BaseUserQueries.fetchBaseUserOfPlayer(playerUUID)).isPresent());
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> TestConstants.REGISTER_TIME, TestConstants.PLAYER_ONE_NAME, serverUUID(), () -> null));
Set<UserInfo> userInfo = db().query(UserInfoQueries.fetchUserInformationOfUser(playerUUID));
Set<UserInfo> expected = Collections.singleton(new UserInfo(playerUUID, serverUUID(), TestConstants.REGISTER_TIME, false, null, false));
assertEquals(expected, userInfo);
}
@Test
default void joinAddressIsUpdatedUponSecondLogin() {
assertFalse(db().query(BaseUserQueries.fetchBaseUserOfPlayer(playerUUID)).isPresent());
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> TestConstants.REGISTER_TIME, TestConstants.PLAYER_ONE_NAME, serverUUID(), () -> null));
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> TestConstants.REGISTER_TIME, TestConstants.PLAYER_ONE_NAME, serverUUID(), TestConstants.GET_PLAYER_HOSTNAME));
Set<UserInfo> userInfo = db().query(UserInfoQueries.fetchUserInformationOfUser(playerUUID));
Set<UserInfo> expected = Collections.singleton(new UserInfo(playerUUID, serverUUID(), TestConstants.REGISTER_TIME, false, TestConstants.GET_PLAYER_HOSTNAME.get(), false));
assertEquals(expected, userInfo);
}
@Test
default void joinAddressUpdateIsUniquePerServer() {
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> TestConstants.REGISTER_TIME, TestConstants.PLAYER_ONE_NAME, serverUUID(), () -> null));
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> TestConstants.REGISTER_TIME, TestConstants.PLAYER_ONE_NAME, serverUUID(), TestConstants.GET_PLAYER_HOSTNAME));
db().executeTransaction(new StoreServerInformationTransaction(new Server(TestConstants.SERVER_TWO_UUID, TestConstants.SERVER_TWO_NAME, "", TestConstants.VERSION)));
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> TestConstants.REGISTER_TIME, TestConstants.PLAYER_ONE_NAME, TestConstants.SERVER_TWO_UUID, () -> "example.join.address"));
Set<UserInfo> userInfo = db().query(UserInfoQueries.fetchUserInformationOfUser(playerUUID));
Set<UserInfo> expected = new HashSet<>(Arrays.asList(
new UserInfo(playerUUID, serverUUID(), TestConstants.REGISTER_TIME, false, TestConstants.GET_PLAYER_HOSTNAME.get(), false),
new UserInfo(playerUUID, TestConstants.SERVER_TWO_UUID, TestConstants.REGISTER_TIME, false, "example.join.address", false)
));
assertEquals(expected, userInfo);
}
@Test
default void userInfoTableUpdatesBanStatus() {
@ -267,81 +226,4 @@ public interface UserInfoQueriesTest extends DatabaseTestPreparer {
default void noMinimumRegisterDateIsFetchedWithNoData() {
assertFalse(db().query(BaseUserQueries.minimumRegisterDate()).isPresent());
}
@Test
default void joinAddressQueryHasNoNullValues() {
joinAddressCanBeNull();
Map<String, Integer> expected = Collections.singletonMap("unknown", 1);
Map<String, Integer> result = db().query(UserInfoQueries.joinAddresses());
assertEquals(expected, result);
}
@Test
default void serverJoinAddressQueryHasNoNullValues() {
joinAddressCanBeNull();
Map<String, Integer> expected = Collections.singletonMap("unknown", 1);
Map<String, Integer> result = db().query(UserInfoQueries.joinAddresses(serverUUID()));
assertEquals(expected, result);
}
@Test
default void joinAddressQueryHasDistinctPlayers() {
db().executeTransaction(new StoreServerInformationTransaction(new Server(TestConstants.SERVER_TWO_UUID, TestConstants.SERVER_TWO_NAME, "", TestConstants.VERSION)));
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> TestConstants.REGISTER_TIME, TestConstants.PLAYER_ONE_NAME, serverUUID(), TestConstants.GET_PLAYER_HOSTNAME));
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> TestConstants.REGISTER_TIME, TestConstants.PLAYER_ONE_NAME, TestConstants.SERVER_TWO_UUID, TestConstants.GET_PLAYER_HOSTNAME));
Map<String, Integer> expected = Collections.singletonMap(TestConstants.GET_PLAYER_HOSTNAME.get(), 1);
Map<String, Integer> result = db().query(UserInfoQueries.joinAddresses());
assertEquals(expected, result);
}
@Test
default void joinAddressFilterOptionsAreFetched() {
joinAddressIsUpdatedUponSecondLogin();
List<String> expected = Collections.singletonList(TestConstants.GET_PLAYER_HOSTNAME.get().toLowerCase());
List<String> result = db().query(UserInfoQueries.uniqueJoinAddresses());
assertEquals(expected, result);
}
@Test
default void joinAddressFilterOptionsAreFetchedWhenThereAreMultiple() {
joinAddressIsUpdatedUponSecondLogin();
db().executeTransaction(new StoreServerInformationTransaction(new Server(TestConstants.SERVER_TWO_UUID, TestConstants.SERVER_TWO_NAME, "", TestConstants.VERSION)));
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> TestConstants.REGISTER_TIME, TestConstants.PLAYER_ONE_NAME, serverUUID(), () -> TestConstants.GET_PLAYER_HOSTNAME.get() + "_b"));
db().executeTransaction(new PlayerServerRegisterTransaction(player2UUID, () -> TestConstants.REGISTER_TIME, TestConstants.PLAYER_ONE_NAME, TestConstants.SERVER_TWO_UUID, () -> TestConstants.GET_PLAYER_HOSTNAME.get() + "_a"));
List<String> expected = Arrays.asList(
TestConstants.GET_PLAYER_HOSTNAME.get().toLowerCase() + "_a",
TestConstants.GET_PLAYER_HOSTNAME.get().toLowerCase() + "_b"
);
List<String> result = db().query(UserInfoQueries.uniqueJoinAddresses());
assertEquals(expected, result);
}
@Test
default void joinAddressFilterUUIDsAreFetched() {
joinAddressIsUpdatedUponSecondLogin();
Set<Integer> expected = Set.of(db().query(BaseUserQueries.fetchUserId(playerUUID)));
Set<Integer> result = db().query(UserInfoQueries.userIdsOfPlayersWithJoinAddresses(
Collections.singletonList(TestConstants.GET_PLAYER_HOSTNAME.get().toLowerCase()))
);
assertEquals(expected, result);
}
@Test
default void joinAddressFilterUUIDsAreFetchedWhenUnknown() {
joinAddressCanBeNull();
Set<Integer> expected = Set.of(db().query(BaseUserQueries.fetchUserId(playerUUID)));
Set<Integer> result = db().query(UserInfoQueries.userIdsOfPlayersWithJoinAddresses(
Collections.singletonList("unknown"))
);
assertEquals(expected, result);
}
}

View File

@ -18,8 +18,8 @@ package com.djrapitops.plan.storage.database.queries.analysis;
import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.storage.database.DatabaseTestPreparer;
import com.djrapitops.plan.storage.database.queries.DataStoreQueries;
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 org.junit.jupiter.api.Test;
import utilities.RandomData;
@ -37,7 +37,7 @@ public interface TopListQueriesTest extends DatabaseTestPreparer {
db().executeTransaction(new PlayerServerRegisterTransaction(player2UUID, RandomData::randomTime,
TestConstants.PLAYER_TWO_NAME, serverUUID(), TestConstants.GET_PLAYER_HOSTNAME));
FinishedSession session = RandomData.randomSession(serverUUID(), worlds, playerUUID, player2UUID);
execute(DataStoreQueries.storeSession(session));
db().executeTransaction(new SessionEndTransaction(session));
}
@Test

View File

@ -20,6 +20,7 @@ import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point;
import com.djrapitops.plan.gathering.domain.*;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.KillsTable;
import org.apache.commons.lang3.RandomStringUtils;
@ -28,6 +29,8 @@ import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class RandomData {
@ -37,6 +40,9 @@ public class RandomData {
private static final Random r = new Random();
private static final int JOIN_ADDRESS_COUNT = 50;
private static final List<JoinAddress> JOIN_ADDRESSES = generateJoinAddresses(JOIN_ADDRESS_COUNT);
public static int randomInt(int rangeStart, int rangeEnd) {
return ThreadLocalRandom.current().nextInt(rangeStart, rangeEnd);
}
@ -117,6 +123,7 @@ public class RandomData {
}
extraData.put(MobKillCounter.class, new MobKillCounter());
extraData.put(DeathCounter.class, new DeathCounter());
extraData.put(JoinAddress.class, JOIN_ADDRESSES.get(randomInt(0, JOIN_ADDRESS_COUNT)));
return new FinishedSession(
uuids[0], serverUUID,
start, end, RandomData.randomLong(0, end - start),
@ -197,4 +204,10 @@ public class RandomData {
public static double randomDouble() {
return ThreadLocalRandom.current().nextDouble();
}
public static List<JoinAddress> generateJoinAddresses(int n) {
return IntStream.range(0, n).mapToObj(i -> "join_address_" + i)
.map(JoinAddress::new)
.collect(Collectors.toList());
}
}

View File

@ -25,6 +25,7 @@ import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
@ -139,7 +140,9 @@ public class PlayerOnlineListener implements FabricListener {
ServerUUID serverUUID = serverInfo.getServerUUID();
String joinAddress = address.toString();
if (!joinAddress.isEmpty()) {
joinAddresses.put(playerUUID, joinAddress.substring(0, joinAddress.lastIndexOf(':')));
joinAddress = joinAddress.substring(0, joinAddress.lastIndexOf(':'));
joinAddresses.put(playerUUID, joinAddress);
dbSystem.getDatabase().executeTransaction(new StoreJoinAddressTransaction(joinAddress));
}
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> banned));
} catch (Exception e) {
@ -201,6 +204,7 @@ public class PlayerOnlineListener implements FabricListener {
ActiveSession session = new ActiveSession(playerUUID, serverUUID, time, world, gm);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName()));
session.getExtraData().put(JoinAddress.class, new JoinAddress(getHostName));
sessionCache.cacheSession(playerUUID, session)
.ifPresent(previousSession -> database.executeTransaction(new SessionEndTransaction(previousSession)));

View File

@ -184,7 +184,8 @@ public class PlayerOnlineListener implements Listener {
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName()));
sessionCache.cacheSession(playerUUID, session)
.ifPresent(previousSession -> database.executeTransaction(new SessionEndTransaction(previousSession)));
.map(SessionEndTransaction::new)
.ifPresent(database::executeTransaction);
database.executeTransaction(new NicknameStoreTransaction(
playerUUID, new Nickname(displayName, time, serverUUID),

View File

@ -25,6 +25,7 @@ import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.gathering.listeners.Status;
import com.djrapitops.plan.identification.ServerInfo;
@ -179,12 +180,15 @@ public class PlayerOnlineListener {
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
database.executeTransaction(new StoreJoinAddressTransaction(getHostName));
ActiveSession session = new ActiveSession(playerUUID, serverUUID, time, world, gm);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName()));
session.getExtraData().put(JoinAddress.class, new JoinAddress(getHostName));
sessionCache.cacheSession(playerUUID, session)
.ifPresent(previousSession -> database.executeTransaction(new SessionEndTransaction(previousSession)));
.map(SessionEndTransaction::new)
.ifPresent(database::executeTransaction);
database.executeTransaction(new NicknameStoreTransaction(
playerUUID, new Nickname(displayName, time, serverUUID),