Files
AuxProtect-Folia/src/dev/heliosares/auxprotect/database/SQLManager.java
Internal-Exception fee62529f2 1.1-pre1
2022-04-08 23:36:43 -04:00

1542 lines
44 KiB
Java

package dev.heliosares.auxprotect.database;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import dev.heliosares.auxprotect.IAuxProtect;
import dev.heliosares.auxprotect.utils.BidiMapCache;
import dev.heliosares.auxprotect.utils.MovingAverage;
import dev.heliosares.auxprotect.utils.MySender;
public class SQLManager {
private Connection connection;
private String targetString;
private final IAuxProtect plugin;
private static String tablePrefix;
private boolean mysql;
private boolean isConnected;
private HashMap<String, Integer> worlds = new HashMap<>();
private int nextWid;
private BidiMapCache<Integer, String> uuids = new BidiMapCache<>(10000L, 10000L, true);
private BidiMapCache<Integer, String> usernames = new BidiMapCache<>(10000L, 10000L, true);
private int version;
private static SQLManager instance;
public static SQLManager getInstance() {
return instance;
}
public static final int DBVERSION = 3;
public int getCount() {
return count;
}
public boolean isConnected() {
return isConnected;
}
public int getVersion() {
return version;
}
public boolean isMySQL() {
return mysql;
}
public MovingAverage putTimePerEntry = new MovingAverage(100);
public MovingAverage putTimePerExec = new MovingAverage(100);
public MovingAverage lookupTime = new MovingAverage(100);
private int count;
public static enum TABLE {
AUXPROTECT, AUXPROTECT_SPAM, AUXPROTECT_LONGTERM, AUXPROTECT_ABANDONED, AUXPROTECT_INVENTORY, AUXPROTECT_WORLDS,
AUXPROTECT_UIDS, AUXPROTECT_COMMANDS;
@Override
public String toString() {
return tablePrefix + super.toString().toLowerCase();
}
public boolean hasData() {
switch (this) {
case AUXPROTECT:
case AUXPROTECT_INVENTORY:
case AUXPROTECT_SPAM:
return true;
default:
return false;
}
}
public boolean isOnBungee() {
switch (this) {
case AUXPROTECT:
case AUXPROTECT_COMMANDS:
case AUXPROTECT_LONGTERM:
return true;
default:
return false;
}
}
public boolean hasLocation() {
switch (this) {
case AUXPROTECT:
case AUXPROTECT_ABANDONED:
case AUXPROTECT_INVENTORY:
case AUXPROTECT_SPAM:
case AUXPROTECT_COMMANDS:
return true;
default:
return false;
}
}
public boolean hasActionId() {
switch (this) {
case AUXPROTECT_COMMANDS:
return false;
default:
return true;
}
}
public boolean hasStringTarget() {
switch (this) {
case AUXPROTECT_COMMANDS:
case AUXPROTECT_LONGTERM:
return true;
default:
return false;
}
}
public String getValuesHeader(boolean bungee) {
if (this == TABLE.AUXPROTECT_LONGTERM) {
return "(time, uid, action_id, target)";
} else if (this == TABLE.AUXPROTECT_COMMANDS) {
if (bungee) {
return "(time, uid, target)";
}
return "(time, uid, world_id, x, y, z, target)";
} else if (bungee) {
return "(time, uid, action_id, target_id, data)";
} else if (this == TABLE.AUXPROTECT || this == TABLE.AUXPROTECT_SPAM
|| this == TABLE.AUXPROTECT_INVENTORY) {
return "(time, uid, action_id, world_id, x, y, z, target_id, data)";
} else if (this == TABLE.AUXPROTECT_ABANDONED) {
return "(time, uid, action_id, world_id, x, y, z, target_id)";
}
return null;
}
public String getValuesTemplate(boolean bungee) {
if (this == TABLE.AUXPROTECT_LONGTERM) {
return "(?, ?, ?, ?)";
} else if (this == TABLE.AUXPROTECT_COMMANDS) {
if (bungee) {
return "(?, ?, ?)";
}
return "(?, ?, ?, ?, ?, ?, ?)";
} else if (bungee) {
return "(?, ?, ?, ?, ?)";
} else if (this == TABLE.AUXPROTECT || this == TABLE.AUXPROTECT_SPAM
|| this == TABLE.AUXPROTECT_INVENTORY) {
return "(?, ?, ?, ?, ?, ?, ?, ?, ?)";
} else if (this == TABLE.AUXPROTECT_ABANDONED) {
return "(?, ?, ?, ?, ?, ?, ?, ?)";
}
return null;
}
}
public String holdingConnection;
public long holdingConnectionSince;
public SQLManager(IAuxProtect plugin, String target, String prefix) {
instance = this;
this.plugin = plugin;
this.targetString = target;
if (prefix == null) {
tablePrefix = "";
} else {
tablePrefix = prefix.replaceAll(" ", "_");
if (tablePrefix.length() > 0 && !tablePrefix.endsWith("_")) {
tablePrefix += "_";
}
}
}
public void connect(String user, String pass) throws SQLException {
boolean driver = false;
if (!driver)
try {
Class.forName("org.sqlite.JDBC");
driver = true;
} catch (ClassNotFoundException e1) {
}
if (!driver)
try {
Class.forName("com.mysql.cj.jdbc.Driver");
driver = true;
} catch (ClassNotFoundException e1) {
}
if (!driver)
try {
Class.forName("com.mysql.jdbc.Driver");
driver = true;
} catch (ClassNotFoundException e1) {
}
if (!driver) {
System.err.println("SQL DRIVER NOT FOUND");
}
if (user != null && pass != null) {
mysql = true;
connection = DriverManager.getConnection(targetString, user, pass);
} else {
mysql = false;
connection = DriverManager.getConnection(targetString);
}
init();
isConnected = true;
}
public void connect() throws SQLException {
connect(null, null);
}
public void close() {
isConnected = false;
if (connection != null) {
checkAsync();
synchronized (connection) {
holdingConnectionSince = System.currentTimeMillis();
holdingConnection = "close";
try {
connection.close();
} catch (SQLException e) {
plugin.print(e);
}
holdingConnectionSince = 0;
}
}
}
private void init() throws SQLException {
checkAsync();
synchronized (connection) {
holdingConnectionSince = System.currentTimeMillis();
holdingConnection = "init";
execute("CREATE TABLE IF NOT EXISTS version (time BIGINT,version INTEGER);");
String stmt = "SELECT * FROM version;";
plugin.debug(stmt, 3);
try (Statement statement = connection.createStatement()) {
try (ResultSet results = statement.executeQuery(stmt)) {
long versionTime = 0;
while (results.next()) {
long versionTime_ = results.getLong("time");
int version_ = results.getInt("version");
if (versionTime_ > versionTime) {
version = version_;
versionTime = versionTime_;
}
plugin.debug("Version at " + versionTime_ + " was v" + version_ + ".", 1);
}
}
}
if (version < 1) {
execute("INSERT INTO version (time,version) VALUES (" + System.currentTimeMillis() + ","
+ (version = DBVERSION) + ")");
}
if (version < 2 && !plugin.isBungee()) {
plugin.info("Migrating database to v2");
execute("ALTER TABLE worlds RENAME TO auxprotect_worlds;");
execute("\"INSERT INTO version (time,version) VALUES (" + System.currentTimeMillis() + ","
+ (version = 2) + ")");
plugin.info("Done migrating.");
}
int rowcountformerge = 0;
TABLE[] migrateTablesV3 = new TABLE[] { TABLE.AUXPROTECT, TABLE.AUXPROTECT_SPAM, TABLE.AUXPROTECT_LONGTERM,
TABLE.AUXPROTECT_ABANDONED, TABLE.AUXPROTECT_INVENTORY };
if (plugin.isBungee()) {
migrateTablesV3 = new TABLE[] { TABLE.AUXPROTECT, TABLE.AUXPROTECT_LONGTERM };
}
if (version < 3) {
plugin.info("Migrating database to v3. DO NOT INTERRUPT");
for (TABLE table : migrateTablesV3) {
rowcountformerge += count(table);// TODO: Lock error maybe?
execute("ALTER TABLE " + table.toString() + " RENAME TO " + table.toString() + "_temp;");
plugin.info(".");
}
plugin.info("Tables renamed");
}
stmt = "CREATE TABLE IF NOT EXISTS " + TABLE.AUXPROTECT.toString() + " (\n";
stmt += " time BIGINT(255),\n";
stmt += " uid integer,\n";
stmt += " action_id SMALLINT,\n";
if (!plugin.isBungee()) {
stmt += " world_id SMALLINT,\n";
stmt += " x INTEGER,\n";
stmt += " y SMALLINT,\n";
stmt += " z INTEGER,\n";
}
stmt += " target_id integer,\n";
stmt += " data LONGTEXT\n";
stmt += ");";
execute(stmt);
stmt = "CREATE TABLE IF NOT EXISTS " + TABLE.AUXPROTECT_LONGTERM.toString() + " (\n";
stmt += " time BIGINT(255),\n";
stmt += " uid integer,\n";
stmt += " action_id SMALLINT,\n";
stmt += " target varchar(255)\n";
stmt += ");";
execute(stmt);
stmt = "CREATE TABLE IF NOT EXISTS " + TABLE.AUXPROTECT_COMMANDS.toString() + " (\n";
stmt += " time BIGINT(255),\n";
stmt += " uid integer,\n";
if (!plugin.isBungee()) {
stmt += " world_id SMALLINT,\n";
stmt += " x INTEGER,\n";
stmt += " y SMALLINT,\n";
stmt += " z INTEGER,\n";
}
stmt += " target LONGTEXT\n";
stmt += ");";
execute(stmt);
if (!plugin.isBungee()) {
stmt = "CREATE TABLE IF NOT EXISTS " + TABLE.AUXPROTECT_INVENTORY.toString() + " (\n";
stmt += " time BIGINT(255),\n";
stmt += " uid integer,\n";
stmt += " action_id SMALLINT,\n";
stmt += " world_id SMALLINT,\n";
stmt += " x INTEGER,\n";
stmt += " y SMALLINT,\n";
stmt += " z INTEGER,\n";
stmt += " target_id integer,\n";
stmt += " data LONGTEXT\n";
stmt += ");";
plugin.debug(stmt, 3);
execute(stmt);
stmt = "CREATE TABLE IF NOT EXISTS " + TABLE.AUXPROTECT_SPAM.toString() + " (\n";
stmt += " time BIGINT(255),\n";
stmt += " uid integer,\n";
stmt += " action_id SMALLINT,\n";
stmt += " world_id SMALLINT,\n";
stmt += " x INTEGER,\n";
stmt += " y SMALLINT,\n";
stmt += " z INTEGER,\n";
stmt += " target_id integer,\n";
stmt += " data LONGTEXT\n";
stmt += ");";
plugin.debug(stmt, 3);
execute(stmt);
if (plugin.getAPConfig().isPrivate()) {
stmt = "CREATE TABLE IF NOT EXISTS " + TABLE.AUXPROTECT_ABANDONED.toString() + " (\n";
stmt += " time BIGINT(255),\n";
stmt += " uid integer,\n";
stmt += " action_id SMALLINT,\n";
stmt += " world_id SMALLINT,\n";
stmt += " x INTEGER,\n";
stmt += " y SMALLINT,\n";
stmt += " z INTEGER,\n";
stmt += " target_id integer\n";
stmt += ");";
plugin.debug(stmt, 3);
execute(stmt);
}
stmt = "CREATE TABLE IF NOT EXISTS " + TABLE.AUXPROTECT_WORLDS.toString()
+ " (name varchar(255), wid SMALLINT);";
plugin.debug(stmt, 3);
execute(stmt);
stmt = "SELECT * FROM " + TABLE.AUXPROTECT_WORLDS.toString() + ";";
plugin.debug(stmt, 3);
try (Statement statement = connection.createStatement()) {
try (ResultSet results = statement.executeQuery(stmt)) {
while (results.next()) {
String world = results.getString("name");
int wid = results.getInt("wid");
worlds.put(world, wid);
if (wid >= nextWid) {
nextWid = wid + 1;
}
}
}
}
}
stmt = "CREATE TABLE IF NOT EXISTS " + TABLE.AUXPROTECT_UIDS.toString()
+ " (uuid varchar(255), uid INTEGER PRIMARY KEY AUTOINCREMENT);";
plugin.debug(stmt, 3);
execute(stmt);
if (version < 3) {
plugin.info("Merging data into new tables...");
int progress = 0;
int count = 0;
for (TABLE table : migrateTablesV3) {
ArrayList<Object[]> output = new ArrayList<>();
ArrayList<Object[]> commands = new ArrayList<>();
final boolean hasLocation = plugin.isBungee() ? false : table.hasLocation();
final boolean hasData = table.hasData();
final boolean hasStringTarget = table.hasStringTarget();
plugin.info("Merging table: " + table.toString());
stmt = "SELECT * FROM " + table.toString() + "_temp;";
plugin.debug(stmt, 3);
try (PreparedStatement pstmt = connection.prepareStatement(stmt)) {
pstmt.setFetchSize(500);
try (ResultSet results = pstmt.executeQuery()) {
while (results.next()) {
ArrayList<Object> entry = new ArrayList<>();
entry.add(results.getLong("time"));
entry.add(this.getUIDFromUUID(results.getString("user"), true));
int action_id = results.getInt("action_id");
if (action_id != 260) {
entry.add(action_id);
}
if (hasLocation) {
entry.add(results.getInt("world_id"));
entry.add(results.getInt("x"));
entry.add(results.getInt("y"));
entry.add(results.getInt("z"));
}
String target = results.getString("target");
if (hasStringTarget || action_id == 260) {
entry.add(target);
} else {
entry.add(this.getUIDFromUUID(target, true));
}
if (hasData) {
entry.add(results.getString("data"));
}
if (action_id == 260) {
commands.add(entry.toArray(new Object[0]));
} else {
output.add(entry.toArray(new Object[0]));
}
if (output.size() >= 5000) {
this.putRaw(table, output);
output.clear();
}
if (commands.size() >= 5000) {
this.putRaw(TABLE.AUXPROTECT_COMMANDS, commands);
commands.clear();
}
count++;
int progressPercentage = (int) Math.floor((double) count / rowcountformerge * 100);
if (progressPercentage / 5 > progress) {
progress = progressPercentage / 5;
plugin.info("Migration " + progress * 5 + "% complete. (" + count + "/"
+ rowcountformerge + "). DO NOT INTERRUPT");
}
}
}
}
if (output.size() > 0) {
this.putRaw(table, output);
}
if (commands.size() > 0) {
this.putRaw(TABLE.AUXPROTECT_COMMANDS, commands);
}
}
execute("INSERT INTO version (time,version) VALUES (" + System.currentTimeMillis() + "," + (version = 3)
+ ")");
plugin.info("Done migrating.");
}
plugin.debug("Purging temporary tables");
for (TABLE table : TABLE.values()) {
execute("DROP TABLE IF EXISTS " + table.toString() + "temp;");
execute("DROP TABLE IF EXISTS " + table.toString() + "_temp;");
}
plugin.debug("init done.", 1);
holdingConnectionSince = 0;
}
}
public void purgeUIDs() {
plugin.info("Performing UID purge");
Set<Integer> inUseUids = new HashSet<>();
Set<Integer> savedUids = new HashSet<>();
synchronized (connection) {
for (TABLE table : new TABLE[] { TABLE.AUXPROTECT, TABLE.AUXPROTECT_SPAM, TABLE.AUXPROTECT_LONGTERM,
TABLE.AUXPROTECT_ABANDONED, TABLE.AUXPROTECT_INVENTORY, TABLE.AUXPROTECT_UIDS }) {
try {
boolean hasTargetId = !table.hasStringTarget() && table != TABLE.AUXPROTECT_UIDS;
String stmt = "SELECT uid" + (hasTargetId ? ", target_id" : "") + " FROM " + table.toString() + ";";
plugin.debug(stmt, 3);
try (PreparedStatement pstmt = connection.prepareStatement(stmt)) {
pstmt.setFetchSize(500);
try (ResultSet results = pstmt.executeQuery()) {
while (results.next()) {
int uid = results.getInt("uid");
if (table == TABLE.AUXPROTECT_UIDS) {
savedUids.add(uid);
continue;
}
inUseUids.add(uid);
if (hasTargetId) {
int target_id = results.getInt("target_id");
inUseUids.add(target_id);
}
}
}
}
} catch (SQLException ignored) {
}
}
plugin.debug(savedUids.size() + " saved UIDS");
plugin.debug(inUseUids.size() + " in use UIDS");
int i = 0;
final String hdr = "DELETE FROM " + TABLE.AUXPROTECT_UIDS.toString() + " WHERE ";
String stmt = "";
try {
for (int uid : savedUids) {
if (inUseUids.contains(uid)) {
continue;
}
if (!stmt.isEmpty()) {
stmt += " OR ";
}
plugin.debug("Purging UID " + uid, 5);
stmt += "uid=" + uid;
if (++i >= 1000) {
execute(hdr + stmt);
stmt = "";
i = 0;
}
}
if (!stmt.isEmpty())
execute(hdr + stmt);
} catch (SQLException e) {
}
}
plugin.info("UID purge complete.");
}
public void vacuum() throws SQLException {
synchronized (connection) {
execute("CREATE TABLE IF NOT EXISTS " + tablePrefix + "last_vacuum (time BIGINT);");
String stmt = "SELECT * FROM " + tablePrefix + "last_vacuum;";
plugin.debug(stmt, 3);
long lastvacuum = 0;
int vacuumcount = 0;
try (Statement statement = connection.createStatement()) {
try (ResultSet results = statement.executeQuery(stmt)) {
while (results.next()) {
long time = results.getLong("time");
if (time > lastvacuum) {
lastvacuum = time;
}
vacuumcount++;
}
}
}
if (System.currentTimeMillis() - lastvacuum > 1000L * 3600L * 24L * 30L) {
plugin.info(
"Performing vacuum operation. Please do not shutdown your server or unload the plugin. This may take a while...");
execute("VACUUM;");
if (vacuumcount == 0) {
execute("INSERT INTO " + tablePrefix + "last_vacuum (time) VALUES (" + System.currentTimeMillis()
+ ");");
} else {
execute("UPDATE " + tablePrefix + "last_vacuum SET time=" + System.currentTimeMillis() + ";");
}
plugin.info("Vacuum complete.");
}
}
}
public void execute(String stmt) throws SQLException {
plugin.debug(stmt, 2);
checkAsync();
synchronized (connection) {
try (Statement statement = connection.createStatement()) {
statement.execute(stmt);
}
}
}
public List<List<String>> executeUpdate(String string) throws SQLException {
plugin.debug(string, 2);
final List<List<String>> rowList = new LinkedList<List<String>>();
checkAsync();
synchronized (connection) {
try (Statement statement = connection.createStatement()) {
try (ResultSet rs = statement.executeQuery(string)) {
final ResultSetMetaData meta = rs.getMetaData();
final int columnCount = meta.getColumnCount();
while (rs.next()) {
final List<String> columnList = new LinkedList<String>();
rowList.add(columnList);
for (int column = 1; column <= columnCount; ++column) {
final Object value = rs.getObject(column);
columnList.add(String.valueOf(value));
}
}
}
}
}
return rowList;
}
private void putRaw(TABLE table, ArrayList<Object[]> datas)
throws SQLException, ClassCastException, IndexOutOfBoundsException {
checkAsync();
synchronized (connection) {
holdingConnectionSince = System.currentTimeMillis();
holdingConnection = "put";
String stmt = "INSERT INTO " + table.toString() + " ";
final boolean hasLocation = plugin.isBungee() ? false : table.hasLocation();
final boolean hasData = table.hasData();
final boolean hasAction = table.hasActionId();
stmt += table.getValuesHeader(plugin.isBungee());
String inc = table.getValuesTemplate(plugin.isBungee());
stmt += " VALUES";
for (int i = 0; i < datas.size(); i++) {
stmt += "\n" + inc;
if (i + 1 == datas.size()) {
stmt += ";";
} else {
stmt += ",";
}
}
try (PreparedStatement statement = connection.prepareStatement(stmt)) {
int i = 1;
for (Object[] data : datas) {
int y = 0;
try {
// statement.setString(i++, table);
statement.setLong(i++, (long) data[y++]);
statement.setInt(i++, (int) data[y++]);
if (hasAction) {
statement.setInt(i++, (int) data[y++]);
}
if (hasLocation) {
statement.setInt(i++, (int) data[y++]);
statement.setInt(i++, (int) data[y++]);
statement.setInt(i++, (int) data[y++]);
statement.setInt(i++, (int) data[y++]);
}
if (table.hasStringTarget()) {
statement.setString(i++, (String) data[y++]);
} else {
statement.setInt(i++, (int) data[y++]);
}
if (hasData) {
statement.setString(i++, (String) data[y++]);
}
} catch (Exception e) {
String error = "";
for (Object o : data) {
error += o + ", ";
}
plugin.warning(error + "\nError at index " + y);
throw e;
}
}
statement.executeUpdate();
}
count += datas.size();
holdingConnectionSince = 0;
}
}
protected void put(TABLE table, ArrayList<DbEntry> entries) throws SQLException {
long start = System.nanoTime();
checkAsync();
synchronized (connection) {
holdingConnectionSince = System.currentTimeMillis();
holdingConnection = "put";
String stmt = "INSERT INTO " + table.toString() + " ";
String inc = table.getValuesTemplate(plugin.isBungee());
final boolean hasLocation = plugin.isBungee() ? false : table.hasLocation();
final boolean hasData = table.hasData();
final boolean hasAction = table.hasActionId();
stmt += table.getValuesHeader(plugin.isBungee());
stmt += " VALUES";
for (int i = 0; i < entries.size(); i++) {
stmt += "\n" + inc;
if (i + 1 == entries.size()) {
stmt += ";";
} else {
stmt += ",";
}
}
try (PreparedStatement statement = connection.prepareStatement(stmt)) {
int i = 1;
for (DbEntry dbEntry : entries) {
// statement.setString(i++, table);
statement.setLong(i++, dbEntry.getTime());
statement.setInt(i++, getUIDFromUUID(dbEntry.getUserUUID(), true));
int action = dbEntry.getState() ? dbEntry.getAction().idPos : dbEntry.getAction().id;
if (hasAction) {
statement.setInt(i++, action);
}
if (hasLocation) {
statement.setInt(i++, getWID(dbEntry.world));
statement.setInt(i++, dbEntry.x);
int y = dbEntry.y;
if (y > 32767) {
y = 32767;
}
if (y < -32768) {
y = -32768;
}
statement.setInt(i++, y);
statement.setInt(i++, dbEntry.z);
}
if (table.hasStringTarget()) {
statement.setString(i++, dbEntry.getTargetUUID());
} else {
statement.setInt(i++, getUIDFromUUID(dbEntry.getTargetUUID(), true));
}
if (hasData) {
statement.setString(i++, dbEntry.getData());
}
}
statement.executeUpdate();
}
count += entries.size();
holdingConnectionSince = 0;
this.putTimePerEntry.addData((System.nanoTime() - start) / (double) entries.size());
this.putTimePerExec.addData(System.nanoTime() - start);
}
}
public enum LookupExceptionType {
SYNTAX, PLAYER_NOT_FOUND, ACTION_NEGATE, UNKNOWN_ACTION, ACTION_INCOMPATIBLE, UNKNOWN_WORLD, UNKNOWN_TABLE,
GENERAL, TOO_MANY
}
public static class LookupException extends Exception {
private static final long serialVersionUID = -8329753973868577238L;
public final LookupExceptionType error;
public final String errorMessage;
private LookupException(LookupExceptionType error, String errorMessage) {
this.error = error;
this.errorMessage = errorMessage;
}
@Override
public String toString() {
return error.toString() + ": " + errorMessage;
}
}
public ArrayList<DbEntry> lookup(HashMap<String, String> params, Location location, boolean exact)
throws LookupException {
long start = System.nanoTime();
if (!isConnected)
return null;
try {
TABLE table = null;
TABLE forcedTable = null;
HashMap<String, ArrayList<String>> dos = new HashMap<>();
HashMap<String, ArrayList<String>> donts = new HashMap<>();
ArrayList<String> writeParams = new ArrayList<>();
ArrayList<String> targets = new ArrayList<>();
boolean targetNot = false;
if (exact && !params.containsKey("radius")) {
if (location == null) {
return null;
}
params.put("radius", "0");
}
for (Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key.equalsIgnoreCase("time") || key.equalsIgnoreCase("after")) {
ArrayList<String> dos_ = donts.get(key);
if (dos_ == null) {
dos_ = new ArrayList<>();
dos.put(key, dos_);
}
dos_.add("time >= " + value);
continue;
} else if (key.equalsIgnoreCase("before")) {
ArrayList<String> dos_ = donts.get(key);
if (dos_ == null) {
dos_ = new ArrayList<>();
dos.put(key, dos_);
}
dos_.add("time <= " + value);
continue;
} else if (key.equalsIgnoreCase("db")) {
try {
forcedTable = TABLE.valueOf(value.toUpperCase());
} catch (IllegalArgumentException e) {
throw new LookupException(LookupExceptionType.SYNTAX,
plugin.translate("lookup-invalid-syntax"));
}
continue;
}
boolean not = value.startsWith("!");
if (not) {
value = value.substring(1);
if (key.equalsIgnoreCase("action")) {
throw new LookupException(LookupExceptionType.ACTION_NEGATE,
plugin.translate("lookup-action-negate"));
}
}
for (String param : value.split(",")) {
String theStmt = "";
if (key.equalsIgnoreCase("target")) {
if (not) {
targetNot = true;
}
targets.add(param);
} else if (key.equalsIgnoreCase("user")) {
int uid = this.getUIDFromUsername(param);
int altuid = this.getUIDFromUUID(param);
if (uid > 0 && altuid > 0) {
theStmt = "(uid = '" + uid + "' OR uid = '" + altuid + "')";
} else if (uid > 0) {
theStmt = "uid = '" + uid + "'";
} else if (altuid > 0) {
theStmt = "uid = '" + altuid + "'";
} else {
throw new LookupException(LookupExceptionType.PLAYER_NOT_FOUND,
String.format(plugin.translate("lookup-playernotfound"), param));
}
} else if (key.equalsIgnoreCase("radius")) {
String rStatement = stmtForRadius(location, param, !params.containsKey("world"), exact);
if (rStatement != null) {
theStmt = rStatement;
}
} else if (key.equalsIgnoreCase("action")) {
String originalParam = param.toString();
boolean state = param.startsWith("+");
if (state || param.startsWith("-")) {
param = param.substring(1);
}
EntryAction action = EntryAction.valueOfString(param);
if (action == null || !action.isEnabled()) {
throw new LookupException(LookupExceptionType.UNKNOWN_ACTION,
String.format(plugin.translate("lookup-unknownaction"), param));
} else {
/*
* TODO if (sender != null) { if
* (!MyPermission.LOOKUP.hasPermission(action.toString().toLowerCase(), sender))
* { sender.sendMessage(String.format(plugin.translate("lookup-action-perm"),
* param)); return null; } }
*/
if (table == null) {
table = action.getTable();
} else {
if (table != action.getTable()) {
throw new LookupException(LookupExceptionType.ACTION_INCOMPATIBLE,
plugin.translate("lookup-incompatible-tables"));
}
}
if (action.hasDual) {
if (!param.equals(originalParam)) {
theStmt = "action_id = " + (state ? action.idPos : action.id);
} else {
theStmt = "action_id = " + action.id + " OR action_id = " + action.idPos;
}
} else {
theStmt = "action_id = " + action.id;
}
}
} else if (key.equalsIgnoreCase("world")) {
int wid = getWID(param);
if (wid == -1) {
throw new LookupException(LookupExceptionType.UNKNOWN_WORLD,
String.format(plugin.translate("lookup-unknown-world"), param));
}
theStmt = "world_id = " + getWID(param);
} else {
theStmt = key + " = ?";
writeParams.add(param);
}
if (theStmt.length() > 0) {
if (not) {
ArrayList<String> donts_ = donts.get(key);
if (donts_ == null) {
donts_ = new ArrayList<>();
donts.put(key, donts_);
}
donts_.add(theStmt);
} else {
ArrayList<String> dos_ = dos.get(key);
if (dos_ == null) {
dos_ = new ArrayList<>();
dos.put(key, dos_);
}
dos_.add(theStmt);
}
}
}
}
if (forcedTable != null) {
table = forcedTable;
} else if (table == null) {
table = TABLE.AUXPROTECT;
}
ArrayList<String> targetArray = new ArrayList<>();
for (String target : targets) {
String theStmt = "";
if (table.hasStringTarget()) {
if (target.contains("*")) {
theStmt = "target LIKE ? OR target LIKE ?";
writeParams.add(target.replaceAll("-", " ").replaceAll("\\*", "%"));
writeParams.add(target.replaceAll("\\*", "%"));
} else {
if (theStmt.length() > 0) {
theStmt += " OR ";
}
theStmt += "lower(target) = ? OR lower(target) = ?";
writeParams.add(target.toLowerCase());
writeParams.add(target.toLowerCase().replaceAll("-", " "));
}
} else {
int uid = this.getUIDFromUsername(target);
int altuid = this.getUIDFromUUID(target);
if (uid > 0 && altuid > 0) {
theStmt = "(target_id = '" + uid + "' OR target_id = '" + altuid + "')";
} else if (uid > 0) {
theStmt = "target_id = '" + uid + "'";
} else if (altuid > 0) {
theStmt = "target_id = '" + altuid + "'";
} else {
throw new LookupException(LookupExceptionType.PLAYER_NOT_FOUND,
String.format(plugin.translate("lookup-playernotfound"), target));
}
}
if (theStmt.length() > 0) {
targetArray.add(theStmt);
}
}
if (targetArray.size() > 0) {
if (targetNot) {
donts.put("target", targetArray);
} else {
dos.put("target", targetArray);
}
}
String stmt = "\nWHERE (";
int conditions = 0;
int i = 0;
if (dos.size() > 0) {
stmt += "(";
for (String key : dos.keySet()) {
if (key.equalsIgnoreCase("action") && !table.hasActionId()) {
continue;
}
if (i > 0) {
stmt += ") AND (";
}
ArrayList<String> values = dos.get(key);
for (int i1 = 0; i1 < values.size(); i1++) {
if (i1 > 0) {
stmt += " OR ";
}
stmt += values.get(i1);
}
i++;
conditions++;
}
stmt += ")";
}
i = 0;
if (donts.size() > 0) {
if (donts.size() > 0) {
stmt += " AND ";
}
stmt += "NOT (";
for (String key : donts.keySet()) {
if (key.equalsIgnoreCase("action") && !table.hasActionId()) {
continue;
}
if (i > 0) {
stmt += " OR ";
}
ArrayList<String> values = donts.get(key);
for (int i1 = 0; i1 < values.size(); i1++) {
if (i1 > 0) {
stmt += " OR ";
}
stmt += values.get(i1);
}
i++;
conditions++;
}
stmt += ")";
}
if (conditions == 0) {
stmt = "";
} else {
stmt += ")";
}
final boolean hasLocation = plugin.isBungee() ? false : table.hasLocation();
final boolean hasData = table.hasData();
final boolean hasAction = table.hasActionId();
if (table == TABLE.AUXPROTECT_WORLDS) {
return null;
} else {
stmt = "SELECT * FROM " + table.toString() + stmt;
}
stmt += "\nORDER BY time DESC\nLIMIT 100001;";
plugin.debug(stmt, 3);
ArrayList<DbEntry> output = new ArrayList<>();
long parseStart;
checkAsync();
synchronized (connection) {
holdingConnectionSince = System.currentTimeMillis();
holdingConnection = "lookup";
long lookupStart = System.currentTimeMillis();
try (PreparedStatement pstmt = connection.prepareStatement(stmt)) {
pstmt.setFetchSize(500);
for (int i1 = 0; i1 < writeParams.size(); i1++) {
String param = writeParams.get(i1);
pstmt.setString(i1 + 1, param);
}
try (ResultSet rs = pstmt.executeQuery()) {
int count = 0;
parseStart = System.currentTimeMillis();
while (rs.next()) {
long time = rs.getLong("time");
int uid = rs.getInt("uid");
int action_id = -1;
if (hasAction) {
action_id = rs.getInt("action_id");
} else if (table == TABLE.AUXPROTECT_COMMANDS) {
action_id = EntryAction.COMMAND.id;
}
String world = null;
int x = 0, y = 0, z = 0;
if (hasLocation) {
world = this.getWorld(rs.getInt("world_id"));
x = rs.getInt("x");
y = rs.getInt("y");
z = rs.getInt("z");
}
String target = null;
String data = null;
if (hasData) {
data = rs.getString("data");
}
EntryAction entryAction = EntryAction.fromId(action_id);
if (entryAction == null) {
plugin.debug("Unknown action_id: " + action_id, 1);
continue;
}
boolean state = false;
if (entryAction.hasDual && entryAction.id != action_id) {
state = true;
}
DbEntry entry = null;
if (table.hasStringTarget()) {
target = rs.getString("target");
entry = new DbEntry(time, uid, entryAction, state, world, x, y, z, target, data);
} else {
int target_id = rs.getInt("target_id");
entry = new DbEntry(time, uid, entryAction, state, world, x, y, z, target_id, data);
}
output.add(entry);
if (++count >= 100000) {
throw new LookupException(LookupExceptionType.TOO_MANY,
String.format(plugin.translate("lookup-toomany"), count));
}
}
}
} catch (SQLException e) {
plugin.warning("Error while executing command");
plugin.warning("SQL Code: " + stmt);
plugin.print(e);
holdingConnectionSince = 0;
throw new LookupException(LookupExceptionType.GENERAL, plugin.translate("lookup-error"));
}
plugin.debug("Completed lookup. Total: " + (System.currentTimeMillis() - lookupStart) + "ms Lookup: "
+ (parseStart - lookupStart) + "ms Parse: " + (System.currentTimeMillis() - parseStart) + "ms",
1);
holdingConnectionSince = 0;
this.lookupTime.addData(System.nanoTime() - start);
return output;
}
} catch (Exception e) {
if (e instanceof LookupException) {
throw e;
}
plugin.warning("Error while executing command");
plugin.print(e);
holdingConnectionSince = 0;
throw new LookupException(LookupExceptionType.GENERAL, plugin.translate("lookup-error"));
}
}
public boolean purge(MySender sender, TABLE table, long time) throws SQLException {
if (!isConnected)
return false;
if (time < 1000 * 3600 * 24 * 14) {
return false;
}
checkAsync();
synchronized (connection) {
holdingConnectionSince = System.currentTimeMillis();
holdingConnection = "purge";
String stmt = "DELETE FROM " + table.toString();
stmt += "\nWHERE (time < ";
stmt += (System.currentTimeMillis() - time);
stmt += ");";
plugin.debug(stmt, 1);
try (PreparedStatement pstmt = connection.prepareStatement(stmt)) {
pstmt.setFetchSize(500);
pstmt.execute();
} catch (SQLException e) {
plugin.print(e);
return false;
}
holdingConnectionSince = 0;
}
return true;
}
public void removeEntry(TABLE table, DbEntry entry) {
if (!isConnected)
return;
String stmt = "DELETE FROM " + table.toString()
+ "\nWHERE time = ? AND uid = ? AND action_id = ? AND world_id = ? AND x = ? AND y = ? AND z = ?;";
plugin.debug(stmt, 3);
checkAsync();
synchronized (connection) {
try {
PreparedStatement statement = connection.prepareStatement(stmt);
int i = 1;
statement.setLong(i++, entry.getTime());
statement.setInt(i++, entry.getUid());
statement.setInt(i++, entry.getAction().getId(entry.getState()));
statement.setInt(i++, getWID(entry.world));
statement.setInt(i++, entry.x);
statement.setInt(i++, entry.y);
statement.setInt(i++, entry.z);
statement.executeUpdate();
} catch (SQLException e) {
plugin.print(e);
}
}
}
private String stmtForRadius(Location location, String radiusStr, boolean specifyWorld, boolean exact) {
int radius = -1;
try {
radius = Integer.parseInt(radiusStr);
} catch (NumberFormatException e) {
}
if (radius < 0 || radius > 250) {
return null;
}
String stmt = "(";
if (location != null) {
stmt += "x BETWEEN " + (location.getBlockX() - radius) + " AND " + (location.getBlockX() + radius);
stmt += " AND ";
if (exact) {
stmt += "y BETWEEN " + (location.getBlockY() - radius) + " AND " + (location.getBlockY() + radius);
stmt += " AND ";
}
stmt += "z BETWEEN " + (location.getBlockZ() - radius) + " AND " + (location.getBlockZ() + radius);
if (specifyWorld) {
stmt += " AND ";
stmt += "world_id = " + getWID(location.getWorld().getName());
}
}
stmt += ")";
return stmt;
}
public void updateUsernameAndIP(String uuid, String name, String ip) {
if (!uuid.startsWith("$")) {
uuid = "$" + uuid;
}
final int uid = this.getUIDFromUUID(uuid, true);
if (uid <= 0) {
return;
}
usernames.put(uid, name);
checkAsync();
synchronized (connection) {
String newestusername = null;
long newestusernametime = 0;
boolean newip = true;
String stmt = "SELECT * FROM " + TABLE.AUXPROTECT_LONGTERM.toString() + " WHERE uid=?;";
plugin.debug(stmt, 3);
try (PreparedStatement pstmt = connection.prepareStatement(stmt)) {
pstmt.setInt(1, uid);
try (ResultSet results = pstmt.executeQuery()) {
while (results.next()) {
String target = results.getString("target");
if (target == null) {
continue;
}
long time = results.getLong("time");
int action_id = results.getInt("action_id");
if (action_id == EntryAction.IP.id) {
if (target.equals(ip)) {
newip = false;
}
} else if (action_id == EntryAction.USERNAME.id) {
if (time > newestusernametime) {
newestusername = target;
newestusernametime = time;
}
}
}
} catch (SQLException e) {
plugin.print(e);
}
} catch (SQLException e) {
plugin.print(e);
}
if (newip) {
plugin.add(new DbEntry(uuid, EntryAction.IP, false, "", 0, 0, 0, ip, ""));
}
if (!name.equalsIgnoreCase(newestusername)) {
plugin.debug("New username: " + name + " for " + newestusername);
plugin.add(new DbEntry(uuid, EntryAction.USERNAME, false, "", 0, 0, 0, name, ""));
}
}
}
public String getUsernameFromUID(int uid) {
if (uid < 0) {
return null;
}
if (uid == 0) {
return "";
}
if (usernames.containsKey(uid)) {
return usernames.get(uid);
}
/*
* if (plugin.isBungee()) { return uuid; } else { OfflinePlayer player =
* Bukkit.getOfflinePlayer(UUID.fromString(uuid.substring(1))); if (player !=
* null) { usernames.put(uuid, player.getName()); return player.getName
*/
String stmt = "SELECT * FROM " + TABLE.AUXPROTECT_LONGTERM.toString()
+ " WHERE action_id=? AND uid=?\nORDER BY time DESC\nLIMIT 1;";
plugin.debug(stmt, 3);
checkAsync();
synchronized (connection) {
try (PreparedStatement pstmt = connection.prepareStatement(stmt)) {
pstmt.setInt(1, EntryAction.USERNAME.id);
pstmt.setInt(2, uid);
try (ResultSet results = pstmt.executeQuery()) {
if (results.next()) {
String username = results.getString("target");
plugin.debug("Resolved UID " + uid + " to " + username, 5);
if (username != null) {
usernames.put(uid, username);
return username;
}
}
} catch (SQLException e) {
plugin.print(e);
}
} catch (SQLException e) {
plugin.print(e);
}
}
return null;
}
public int getUIDFromUsername(String username) {
if (username == null) {
return -1;
}
if (usernames.containsValue(username)) {
return usernames.getKey(username);
}
String stmt = "SELECT * FROM " + TABLE.AUXPROTECT_LONGTERM.toString()
+ " WHERE action_id=? AND lower(target)=?\nORDER BY time DESC\nLIMIT 1;";
plugin.debug(stmt, 3);
checkAsync();
synchronized (connection) {
try (PreparedStatement pstmt = connection.prepareStatement(stmt)) {
pstmt.setInt(1, EntryAction.USERNAME.id);
pstmt.setString(2, username.toLowerCase());
try (ResultSet results = pstmt.executeQuery()) {
if (results.next()) {
int uid = results.getInt("uid");
String username_ = results.getString("target");
plugin.debug("Resolved username " + username_ + " to UID " + uid, 5);
if (username_ != null && uid > 0) {
usernames.put(uid, username_);
return uid;
}
}
} catch (SQLException e) {
plugin.print(e);
}
} catch (SQLException e) {
plugin.print(e);
}
}
plugin.debug("Unknown UID for " + username, 3);
return -1;
}
public int getWID(String world) {
if (worlds.containsKey(world)) {
return worlds.get(world);
}
if (Bukkit.getWorld(world) == null) {
return -1;
}
checkAsync();
synchronized (connection) {
try {
String stmt = "INSERT INTO " + TABLE.AUXPROTECT_WORLDS.toString() + " (name, wid)";
stmt += "\nVALUES (?,?)";
PreparedStatement pstmt = connection.prepareStatement(stmt);
pstmt.setString(1, world);
pstmt.setInt(2, nextWid);
plugin.debug(stmt + "\n" + world + ":" + nextWid, 3);
pstmt.execute();
worlds.put(world, nextWid);
count++;
return nextWid++;
} catch (SQLException e) {
plugin.print(e);
}
}
return -1;
}
public String getWorld(int wid) {
for (Entry<String, Integer> entry : worlds.entrySet()) {
if (entry.getValue() == wid) {
return entry.getKey();
}
}
return null;
}
public int getUIDFromUUID(String uuid) {
return getUIDFromUUID(uuid, false);
}
public int getUIDFromUUID(String uuid, boolean insert) {
if (uuid == null || uuid.equalsIgnoreCase("#null")) {
return -1;
}
if (uuid.length() == 0) {
return 0;
}
uuid = uuid.toLowerCase();
if (uuids.containsValue(uuid)) {
return uuids.getKey(uuid);
}
checkAsync();
synchronized (connection) {
String stmt = "SELECT * FROM " + TABLE.AUXPROTECT_UIDS.toString() + " WHERE uuid=?;";
plugin.debug(stmt, 3);
try (PreparedStatement pstmt = connection.prepareStatement(stmt)) {
pstmt.setString(1, uuid);
try (ResultSet results = pstmt.executeQuery()) {
if (results.next()) {
int uid = results.getInt("uid");
uuids.put(uid, uuid);
return uid;
}
} catch (SQLException e) {
plugin.print(e);
}
} catch (SQLException e) {
plugin.print(e);
}
if (insert) {
stmt = "INSERT INTO " + TABLE.AUXPROTECT_UIDS.toString() + " (uuid)\nVALUES (?)";
try (PreparedStatement pstmt = connection.prepareStatement(stmt)) {
pstmt.setString(1, uuid);
pstmt.execute();
try (ResultSet result = pstmt.getGeneratedKeys()) {
if (result.next()) {
int uid = result.getInt(1);
uuids.put(uid, uuid);
plugin.debug("New UUID: " + uuid + ":" + uid, 3);
count++;
return uid++;
}
}
} catch (SQLException e) {
plugin.print(e);
}
}
}
return -1;
}
public String getUUIDFromUID(int uid) {
if (uid < 0) {
return "#null";
}
if (uid == 0) {
return "";
}
if (uuids.containsKey(uid)) {
return uuids.get(uid);
}
checkAsync();
synchronized (connection) {
try (Statement statement = connection.createStatement()) {
String stmt = "SELECT * FROM " + TABLE.AUXPROTECT_UIDS.toString() + " WHERE uid='" + uid + "';";
plugin.debug(stmt, 3);
try (ResultSet results = statement.executeQuery(stmt)) {
if (results.next()) {
String uuid = results.getString("uuid");
uuids.put(uid, uuid);
return uuid;
}
} catch (SQLException e) {
plugin.print(e);
}
} catch (SQLException e) {
plugin.print(e);
}
}
return null;
}
public void count() throws SQLException {
int total = 0;
plugin.debug("Counting rows..");
for (TABLE table : TABLE.values()) {
if (plugin.isBungee() && !table.isOnBungee()) {
continue;
}
if (table == TABLE.AUXPROTECT_ABANDONED && !plugin.getAPConfig().isPrivate()) {
continue;
}
total += count(table);
}
plugin.debug("Counted all tables. " + total + " rows.");
count = total;
}
public int count(TABLE table) throws SQLException {
String stmtStr = "";
if (mysql) {
stmtStr = "SELECT COUNT(*) FROM " + table.toString();
} else {
stmtStr = "SELECT COUNT(1) FROM " + table.toString();
}
plugin.debug(stmtStr, 5);
checkAsync();
synchronized (connection) {
holdingConnectionSince = System.currentTimeMillis();
holdingConnection = "count";
try (PreparedStatement pstmt = connection.prepareStatement(stmtStr)) {
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return rs.getInt(1);
}
}
}
holdingConnectionSince = 0;
}
return -1;
}
public void cleanup() {
usernames.cleanup();
uuids.cleanup();
}
private boolean checkAsync() {
if (plugin.isBungee()) {
return true;
}
boolean sync = Bukkit.isPrimaryThread();
if (sync) {
plugin.warning("Synchronous call to database. This may cause lag.");
if (plugin.getDebug() > 0) {
Thread.dumpStack();
}
}
return !sync;
}
}