Fixing SQL load errors

This commit is contained in:
2026-04-06 12:52:42 -04:00
parent 756b3b04c7
commit 9e68524bd7
7 changed files with 134 additions and 62 deletions
+2 -3
View File
@@ -63,7 +63,7 @@
<goal>shade</goal> <goal>shade</goal>
</goals> </goals>
<configuration> <configuration>
<minimizeJar>true</minimizeJar> <minimizeJar>false</minimizeJar>
<relocations> <relocations>
<relocation> <relocation>
<pattern>org.yaml.snakeyaml</pattern> <pattern>org.yaml.snakeyaml</pattern>
@@ -148,7 +148,7 @@
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>
<version>9.1.0</version> <version>9.3.0</version>
<type>jar</type> <type>jar</type>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
@@ -182,7 +182,6 @@
<version>3.1.8</version> <version>3.1.8</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mongodb</groupId> <groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId> <artifactId>mongo-java-driver</artifactId>
@@ -57,10 +57,10 @@ import java.util.List;
@MavenLibrary( @MavenLibrary(
groupId = "com.mysql", groupId = "com.mysql",
artifactId = "mysql-connector-j", artifactId = "mysql-connector-j",
version = "9.1.0", version = "9.3.0",
relocations = { relocations = {
@Relocate(from = "com.my\\" + "sql.cj", to = "dev.brighten.antivpn.shaded.com.mysql.cj"), @Relocate(from = "com.my\\" + "sql.cj", to = "dev.brighten.antivpn.shaded.com.mysql.cj"),
@Relocate(from = "com.my\\" + "sql.jdbc", to = "dev.brighten.antivpn.shaded.com.mysql.jdbc") @Relocate(from = "com.my\\" + "sql.jdbc", to = "dev.brighten.antivpn.shaded.com.mysql.jdbc"),
} }
) )
@MavenLibrary(groupId = "com.\\github\\.ben-manes\\.caffeine", artifactId = "caffeine", version = "3.1.8", @MavenLibrary(groupId = "com.\\github\\.ben-manes\\.caffeine", artifactId = "caffeine", version = "3.1.8",
@@ -359,15 +359,37 @@ public class H2VPN implements VPNDatabase {
public void backupDatabase() { public void backupDatabase() {
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases"); File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
if(!dataFolder.exists()) { if(!dataFolder.exists() || MySQL.isClosed()) {
return; return;
} }
List<File> files = new ArrayList<>(List.of(Optional.ofNullable(dataFolder.listFiles()).orElse(new File[0]))); try {
files.sort(Comparator.comparingLong(File::lastModified)); var connection = Query.getConn();
if (connection == null || connection.getMetaData() == null
|| !connection.getMetaData().getDatabaseProductName().equalsIgnoreCase("H2")) {
return;
}
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not verify database type before H2 backup.", e);
return;
}
for (File file : files) { File backupDir = new File(dataFolder, "backups");
MySQL.backupOldDB(file, dataFolder); if (!backupDir.exists() && !backupDir.mkdirs()) {
AntiVPN.getInstance().getExecutor().log("Could not create backup directory");
return;
}
File backupFile = new File(backupDir, "database.h2_backup_" + System.currentTimeMillis() + ".zip");
String backupPath = backupFile.getAbsolutePath()
.replace("\\", "/")
.replace("'", "''");
try (var statement = Query.prepare("BACKUP TO '" + backupPath + "'")) {
statement.execute();
AntiVPN.getInstance().getExecutor().log("Created H2 backup at " + backupFile.getName());
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not create H2 backup before migration.", e);
} }
} }
} }
@@ -24,6 +24,7 @@ import dev.brighten.antivpn.database.sql.utils.Query;
import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.database.version.Version;
import dev.brighten.antivpn.utils.MiscUtils; import dev.brighten.antivpn.utils.MiscUtils;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -51,11 +52,11 @@ public class First implements Version<VPNDatabase> {
.append(versionNumber())).execute(); .append(versionNumber())).execute();
AntiVPN.getInstance().getExecutor().log("Creating indexes..."); AntiVPN.getInstance().getExecutor().log("Creating indexes...");
closeOnEnd(Query.prepare("create index if not exists `uuid_1` on `whitelisted` (`uuid`)")).execute(); createIndexIfAbsent("whitelisted", "uuid_1", "`uuid`");
closeOnEnd(Query.prepare("create index if not exists `ip_1` on `responses` (`ip`)")).execute(); createIndexIfAbsent("responses", "ip_1", "`ip`");
closeOnEnd(Query.prepare("create index if not exists `proxy_1` on `responses` (`proxy`)")).execute(); createIndexIfAbsent("responses", "proxy_1", "`proxy`");
closeOnEnd(Query.prepare("create index if not exists `inserted_1` on `responses` (`inserted`)")).execute(); createIndexIfAbsent("responses", "inserted_1", "`inserted`");
closeOnEnd(Query.prepare("create index if not exists `ip_1` on `whitelisted-ips` (`ip`)")).execute(); createIndexIfAbsent("whitelisted-ips", "ip_1", "`ip`");
} catch (SQLException e) { } catch (SQLException e) {
throw new DatabaseException("Failed to update database", e); throw new DatabaseException("Failed to update database", e);
} finally { } finally {
@@ -69,6 +70,47 @@ public class First implements Version<VPNDatabase> {
return statement; return statement;
} }
protected void createIndexIfAbsent(String tableName, String indexName, String columnList) throws SQLException {
if (hasIndex(tableName, indexName)) {
return;
}
closeOnEnd(Query.prepare(String.format(
"create index `%s` on `%s` (%s)",
indexName,
tableName,
columnList
))).execute();
}
protected void dropIndexIfPresent(String tableName, String indexName) throws SQLException {
if (!hasIndex(tableName, indexName)) {
return;
}
closeOnEnd(Query.prepare(String.format(
"drop index `%s` on `%s`",
indexName,
tableName
))).execute();
}
protected boolean hasIndex(String tableName, String indexName) throws SQLException {
DatabaseMetaData metaData = Query.getConn().getMetaData();
try (ResultSet indexes = metaData.getIndexInfo(null, null, tableName, false, false)) {
while (indexes.next()) {
String existingIndexName = indexes.getString("INDEX_NAME");
if (existingIndexName != null && existingIndexName.equalsIgnoreCase(indexName)) {
return true;
}
}
}
return false;
}
@Override @Override
public int versionNumber() { public int versionNumber() {
return 0; return 0;
@@ -78,7 +120,7 @@ public class First implements Version<VPNDatabase> {
public boolean needsUpdate(VPNDatabase database) { public boolean needsUpdate(VPNDatabase database) {
try(var statement = Query.prepare("select * from `database_version` where version = 0")) { try(var statement = Query.prepare("select * from `database_version` where version = 0")) {
try(ResultSet set = statement.executeQuery()) { try(ResultSet set = statement.executeQuery()) {
return set.getFetchSize() == 0; return !set.next();
} }
} catch (SQLException e) { } catch (SQLException e) {
return true; return true;
@@ -20,6 +20,7 @@ import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.database.DatabaseException; import dev.brighten.antivpn.database.DatabaseException;
import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.VPNDatabase;
import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.local.H2VPN;
import dev.brighten.antivpn.database.sql.MySqlVPN;
import dev.brighten.antivpn.database.sql.utils.ExecutableStatement; import dev.brighten.antivpn.database.sql.utils.ExecutableStatement;
import dev.brighten.antivpn.database.sql.utils.Query; import dev.brighten.antivpn.database.sql.utils.Query;
import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.database.version.Version;
@@ -31,12 +32,12 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class Second implements Version<VPNDatabase> { public class Second extends First implements Version<VPNDatabase> {
private final List<AutoCloseable> toClose = new ArrayList<>(); private final List<AutoCloseable> toClose = new ArrayList<>();
@Override @Override
public void update(VPNDatabase database) throws DatabaseException { public void update(VPNDatabase database) throws DatabaseException {
if(database instanceof H2VPN h2VPN) { if(database instanceof H2VPN h2VPN && !(database instanceof MySqlVPN)) {
h2VPN.backupDatabase(); h2VPN.backupDatabase();
} }
List<String> whitelistedIps = new ArrayList<>(); List<String> whitelistedIps = new ArrayList<>();
@@ -58,7 +59,7 @@ public class Second implements Version<VPNDatabase> {
"ip_start BIGINT NOT NULL, " + "ip_start BIGINT NOT NULL, " +
"ip_end BIGINT NOT NULL)")) "ip_end BIGINT NOT NULL)"))
.execute(); .execute();
closeOnEnd(Query.prepare("CREATE INDEX idx_ip_range ON `whitelisted-ranges` (ip_start, ip_end)")).execute(); createIndexIfAbsent("whitelisted-ranges", "idx_ip_range", "ip_start, ip_end");
var cidrs = whitelistedIps.stream().map(ip -> { var cidrs = whitelistedIps.stream().map(ip -> {
try { try {
@@ -84,7 +85,7 @@ public class Second implements Version<VPNDatabase> {
} }
} }
closeOnEnd(Query.prepare("DROP INDEX ip_1 on `whitelisted-ips`")).execute(); dropIndexIfPresent("whitelisted-ips", "ip_1");
closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute(); closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute();
closeOnEnd(Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())).execute(); closeOnEnd(Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())).execute();
} catch (Throwable e) { } catch (Throwable e) {
@@ -109,9 +110,7 @@ public class Second implements Version<VPNDatabase> {
private void rollback(List<String> ipAddresses) throws SQLException { private void rollback(List<String> ipAddresses) throws SQLException {
AntiVPN.getInstance().getExecutor().log("Rolling back to version 0..."); AntiVPN.getInstance().getExecutor().log("Rolling back to version 0...");
try(var statement = Query.prepare("DROP INDEX idx_ip_range ON `whitelisted-ranges`")) { dropIndexIfPresent("whitelisted-ranges", "idx_ip_range");
statement.execute();
}
try(var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) { try(var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) {
statement.execute(); statement.execute();
} }
@@ -124,9 +123,7 @@ public class Second implements Version<VPNDatabase> {
statement.execute(); statement.execute();
} }
try(var statement = Query.prepare("create index if not exists `ip_1` on `whitelisted-ips` (`ip`)")) { createIndexIfAbsent("whitelisted-ips", "ip_1", "`ip`");
statement.execute();
}
try(var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) { try(var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) {
statement.execute(); statement.execute();
@@ -151,7 +148,7 @@ public class Second implements Version<VPNDatabase> {
public boolean needsUpdate(VPNDatabase database) { public boolean needsUpdate(VPNDatabase database) {
try (var statement = Query.prepare("select * from `database_version` where version = 1")) { try (var statement = Query.prepare("select * from `database_version` where version = 1")) {
try(var set = statement.executeQuery()) { try(var set = statement.executeQuery()) {
return set.getFetchSize() == 0; return !set.next();
} }
} catch (SQLException e) { } catch (SQLException e) {
return true; return true;
@@ -106,7 +106,7 @@ public class Third implements Version<VPNDatabase> {
public boolean needsUpdate(VPNDatabase database) { public boolean needsUpdate(VPNDatabase database) {
try (var statement = Query.prepare("select * from `database_version` where version = 2")) { try (var statement = Query.prepare("select * from `database_version` where version = 2")) {
try(var set = statement.executeQuery()) { try(var set = statement.executeQuery()) {
return set.getFetchSize() == 0; return !set.next();
} }
} catch (SQLException e) { } catch (SQLException e) {
return true; return true;
@@ -54,14 +54,11 @@ public class MySQL {
} }
} }
private static boolean didRetry = false;
public static void initH2() { public static void initH2() {
if(didRetry) { initH2(true);
AntiVPN.getInstance().getExecutor().log(Level.WARNING, }
"Already attempted to retry H2 connection, skipping.");
return; private static void initH2(boolean allowRetry) {
}
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases"); File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
if (!dataFolder.exists() && dataFolder.mkdirs()) { if (!dataFolder.exists() && dataFolder.mkdirs()) {
AntiVPN.getInstance().getExecutor().log("Created database directory"); AntiVPN.getInstance().getExecutor().log("Created database directory");
@@ -85,9 +82,13 @@ public class MySQL {
AntiVPN.getInstance().getExecutor() AntiVPN.getInstance().getExecutor()
.log("H2 database file is incompatible with this version of AntiVPN. " + .log("H2 database file is incompatible with this version of AntiVPN. " +
"Backing up old database file..."); "Backing up old database file...");
backupOldDB(dbFile, dataFolder); shutdown();
initH2(); if (allowRetry && backupOldDB(dbFile, dataFolder)) {
didRetry = true; initH2(false);
} else {
AntiVPN.getInstance().getExecutor().log(
"Could not back up and remove the incompatible H2 database file automatically.");
}
} else { } else {
AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + ex.getCause().toString(), ex); AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + ex.getCause().toString(), ex);
} }
@@ -97,31 +98,42 @@ public class MySQL {
} }
} }
public static void backupOldDB(File dbFile, File dataFolder) { public static boolean backupOldDB(File dbFile, File dataFolder) {
if (dbFile.exists()) { if (!dbFile.exists()) {
try { return true;
// Optional: Make backup first
File backupDir = new File(dataFolder, "backups");
if(backupDir.mkdirs()) {
AntiVPN.getInstance().getExecutor().log("Created backup directory");
} else {
AntiVPN.getInstance().getExecutor().log("Backup directory already exists");
}
File backupFile = new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis());
Files.copy(dbFile.toPath(), backupFile.toPath());
// Actually delete the file
if (!dbFile.delete()) {
// If normal delete fails, try force delete on JVM exit
dbFile.deleteOnExit();
AntiVPN.getInstance().getExecutor().log("Could not delete database file - will try again on shutdown");
} else {
AntiVPN.getInstance().getExecutor().log("Successfully deleted incompatible database file");
}
} catch (IOException ex) {
AntiVPN.getInstance().getExecutor().logException("Failed to handle database file", ex);
}
} }
if (!dbFile.isFile()) {
AntiVPN.getInstance().getExecutor().log("Skipping backup for non-file path: " + dbFile.getAbsolutePath());
return false;
}
try {
File backupDir = new File(dataFolder, "backups");
if(backupDir.mkdirs()) {
AntiVPN.getInstance().getExecutor().log("Created backup directory");
} else if (backupDir.exists()) {
AntiVPN.getInstance().getExecutor().log("Backup directory already exists");
} else {
AntiVPN.getInstance().getExecutor().log("Could not create backup directory");
return false;
}
File backupFile = new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis());
Files.copy(dbFile.toPath(), backupFile.toPath());
if (!dbFile.delete()) {
dbFile.deleteOnExit();
AntiVPN.getInstance().getExecutor().log("Could not delete database file - will try again on shutdown");
return false;
}
AntiVPN.getInstance().getExecutor().log("Successfully deleted incompatible database file");
return true;
} catch (IOException ex) {
AntiVPN.getInstance().getExecutor().logException("Failed to handle database file", ex);
}
return false;
} }
public static void use() { public static void use() {