Remove dangling user and server data when patching database to prevent errors

Affects issues:
- Fixed #2335
- Fixed #2333
This commit is contained in:
Aurora Lahtela 2022-04-17 09:28:16 +03:00
parent bbe5a1195e
commit c1c2814971
3 changed files with 244 additions and 1 deletions

View File

@ -198,7 +198,6 @@ public abstract class SQLDB extends AbstractDatabase {
new WorldsOptimizationPatch(),
new KillsOptimizationPatch(),
new NicknamesOptimizationPatch(),
new GeoInfoOptimizationPatch(),
new TransferTableRemovalPatch(),
new BadAFKThresholdValuePatch(),
new DeleteIPsPatch(),
@ -217,6 +216,9 @@ public abstract class SQLDB extends AbstractDatabase {
new RemoveIncorrectTebexPackageDataPatch(),
new ExtensionTableProviderFormattersPatch(),
new ServerPlanVersionPatch(),
new RemoveDanglingUserDataPatch(),
new RemoveDanglingServerDataPatch(),
new GeoInfoOptimizationPatch(),
new PingOptimizationPatch(),
new UserInfoOptimizationPatch(),
new WorldTimesOptimizationPatch(),

View File

@ -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;
}
};
}
}

View File

@ -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;
}
};
}
}