Skip to content

Commit

Permalink
Remove dangling user and server data when patching database to preven…
Browse files Browse the repository at this point in the history
…t errors

Affects issues:
- Fixed #2335
- Fixed #2333
  • Loading branch information
AuroraLS3 committed Apr 17, 2022
1 parent bbe5a11 commit c1c2814
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ Patch[] patches() {
new WorldsOptimizationPatch(),
new KillsOptimizationPatch(),
new NicknamesOptimizationPatch(),
new GeoInfoOptimizationPatch(),
new TransferTableRemovalPatch(),
new BadAFKThresholdValuePatch(),
new DeleteIPsPatch(),
Expand All @@ -217,6 +216,9 @@ Patch[] patches() {
new RemoveIncorrectTebexPackageDataPatch(),
new ExtensionTableProviderFormattersPatch(),
new ServerPlanVersionPatch(),
new RemoveDanglingUserDataPatch(),
new RemoveDanglingServerDataPatch(),
new GeoInfoOptimizationPatch(),
new PingOptimizationPatch(),
new UserInfoOptimizationPatch(),
new WorldTimesOptimizationPatch(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* 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.sql.tables.PingTable;
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable;
import com.djrapitops.plan.storage.database.sql.tables.WorldTimesTable;
import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement;
import com.djrapitops.plan.storage.database.transactions.Executable;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;

/**
* Takes care of data without foreign keys that is missing the foreign key target in plan_servers.
*/
public class RemoveDanglingServerDataPatch extends Patch {

private boolean userInfoTableOk;
private boolean pingTableOk;
private boolean worldTimesTableOk;
private boolean sessionsTableOk;
private boolean pingOptimizationFailed;
private boolean userInfoOptimizationFailed;
private boolean worldTimesOptimizationFailed;
private boolean sessionsOptimizationFailed;

@Override
public boolean hasBeenApplied() {
userInfoTableOk = hasColumn(UserInfoTable.TABLE_NAME, UserInfoTable.SERVER_ID);
pingTableOk = hasColumn(PingTable.TABLE_NAME, PingTable.SERVER_ID);
worldTimesTableOk = hasColumn(WorldTimesTable.TABLE_NAME, WorldTimesTable.SERVER_ID);
sessionsTableOk = hasColumn(SessionsTable.TABLE_NAME, SessionsTable.SERVER_ID);
pingOptimizationFailed = hasTable("temp_ping");
userInfoOptimizationFailed = hasTable("temp_user_info");
worldTimesOptimizationFailed = hasTable("temp_world_times");
sessionsOptimizationFailed = hasTable("temp_sessions");

return userInfoTableOk
&& pingTableOk
&& worldTimesTableOk
&& sessionsTableOk
&& !pingOptimizationFailed
&& !userInfoOptimizationFailed
&& !worldTimesOptimizationFailed
&& !sessionsOptimizationFailed;
}

@Override
protected void applyPatch() {
if (!userInfoTableOk) fixTable(UserInfoTable.TABLE_NAME);
if (!pingTableOk) fixTable(PingTable.TABLE_NAME);
if (!worldTimesTableOk) fixTable(WorldTimesTable.TABLE_NAME);
if (!sessionsTableOk) fixTable(SessionsTable.TABLE_NAME);

if (pingOptimizationFailed) fixTable("temp_ping");
if (userInfoOptimizationFailed) fixTable("temp_user_info");
if (worldTimesOptimizationFailed) fixTable("temp_world_times");
if (sessionsOptimizationFailed) fixTable("temp_sessions");
}

private void fixTable(String tableName) {
Set<String> badUuids = query(getBadUuids(tableName));
if (!badUuids.isEmpty()) {
execute(deleteBadUuids(tableName, badUuids));
}
}

private Executable deleteBadUuids(String tableName, Set<String> badUuids) {
String sql = "DELETE FROM " + tableName + " WHERE server_uuid=?";
return new ExecBatchStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (String badUuid : badUuids) {
statement.setString(1, badUuid);
statement.addBatch();
}
}
};
}

private Query<Set<String>> getBadUuids(String tableName) {
String sql = "SELECT g.uuid FROM " + tableName + " g " +
"LEFT JOIN plan_servers s on s.uuid=g.server_uuid " +
"WHERE s.uuid IS NULL";

return new QueryAllStatement<Set<String>>(sql) {
@Override
public Set<String> processResults(ResultSet set) throws SQLException {
HashSet<String> uuids = new HashSet<>();
while (set.next()) {
uuids.add(set.getString("uuid"));
}
return uuids;
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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.sql.tables.*;
import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement;
import com.djrapitops.plan.storage.database.transactions.Executable;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;

/**
* Takes care of data without foreign keys that is missing the foreign key target in plan_users.
*/
public class RemoveDanglingUserDataPatch extends Patch {

private boolean userInfoTableOk;
private boolean geolocationsTableOk;
private boolean pingTableOk;
private boolean worldTimesTableOk;
private boolean sessionsTableOk;
private boolean pingOptimizationFailed;
private boolean userInfoOptimizationFailed;
private boolean worldTimesOptimizationFailed;
private boolean sessionsOptimizationFailed;
private boolean geolocationOptimizationFailed;

@Override
public boolean hasBeenApplied() {
userInfoTableOk = hasColumn(UserInfoTable.TABLE_NAME, UserInfoTable.USER_ID);
geolocationsTableOk = hasColumn(GeoInfoTable.TABLE_NAME, GeoInfoTable.USER_ID);
pingTableOk = hasColumn(PingTable.TABLE_NAME, PingTable.USER_ID);
worldTimesTableOk = hasColumn(WorldTimesTable.TABLE_NAME, WorldTimesTable.USER_ID);
sessionsTableOk = hasColumn(SessionsTable.TABLE_NAME, SessionsTable.USER_ID);
pingOptimizationFailed = hasTable("temp_ping");
userInfoOptimizationFailed = hasTable("temp_user_info");
worldTimesOptimizationFailed = hasTable("temp_world_times");
sessionsOptimizationFailed = hasTable("temp_sessions");
geolocationOptimizationFailed = hasTable("temp_geoinformation");

return userInfoTableOk
&& geolocationsTableOk
&& pingTableOk
&& worldTimesTableOk
&& sessionsTableOk
&& !pingOptimizationFailed
&& !userInfoOptimizationFailed
&& !worldTimesOptimizationFailed
&& !sessionsOptimizationFailed
&& !geolocationOptimizationFailed;
}

@Override
protected void applyPatch() {
if (!userInfoTableOk) fixTable(UserInfoTable.TABLE_NAME);
if (!geolocationsTableOk) fixTable(GeoInfoTable.TABLE_NAME);
if (!pingTableOk) fixTable(PingTable.TABLE_NAME);
if (!worldTimesTableOk) fixTable(WorldTimesTable.TABLE_NAME);
if (!sessionsTableOk) fixTable(SessionsTable.TABLE_NAME);

if (pingOptimizationFailed) fixTable("temp_ping");
if (userInfoOptimizationFailed) fixTable("temp_user_info");
if (worldTimesOptimizationFailed) fixTable("temp_world_times");
if (sessionsOptimizationFailed) fixTable("temp_sessions");
if (geolocationOptimizationFailed) fixTable("temp_geoinformation");
}

private void fixTable(String tableName) {
Set<String> badUuids = query(getBadUuids(tableName));
if (!badUuids.isEmpty()) {
execute(deleteBadUuids(tableName, badUuids));
}
}

private Executable deleteBadUuids(String tableName, Set<String> badUuids) {
String sql = "DELETE FROM " + tableName + " WHERE uuid=?";
return new ExecBatchStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (String badUuid : badUuids) {
statement.setString(1, badUuid);
statement.addBatch();
}
}
};
}

private Query<Set<String>> getBadUuids(String tableName) {
String sql = "SELECT g.uuid FROM " + tableName + " g " +
"LEFT JOIN plan_users u on u.uuid=g.uuid " +
"WHERE u.uuid IS NULL";

return new QueryAllStatement<Set<String>>(sql) {
@Override
public Set<String> processResults(ResultSet set) throws SQLException {
HashSet<String> uuids = new HashSet<>();
while (set.next()) {
uuids.add(set.getString("uuid"));
}
return uuids;
}
};
}
}

0 comments on commit c1c2814

Please sign in to comment.