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>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<minimizeJar>false</minimizeJar>
<relocations>
<relocation>
<pattern>org.yaml.snakeyaml</pattern>
@@ -148,7 +148,7 @@
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.1.0</version>
<version>9.3.0</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
@@ -182,7 +182,6 @@
<version>3.1.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
@@ -57,10 +57,10 @@ import java.util.List;
@MavenLibrary(
groupId = "com.mysql",
artifactId = "mysql-connector-j",
version = "9.1.0",
version = "9.3.0",
relocations = {
@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",
@@ -359,15 +359,37 @@ public class H2VPN implements VPNDatabase {
public void backupDatabase() {
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
if(!dataFolder.exists()) {
if(!dataFolder.exists() || MySQL.isClosed()) {
return;
}
List<File> files = new ArrayList<>(List.of(Optional.ofNullable(dataFolder.listFiles()).orElse(new File[0])));
files.sort(Comparator.comparingLong(File::lastModified));
try {
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) {
MySQL.backupOldDB(file, dataFolder);
File backupDir = new File(dataFolder, "backups");
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.utils.MiscUtils;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -51,11 +52,11 @@ public class First implements Version<VPNDatabase> {
.append(versionNumber())).execute();
AntiVPN.getInstance().getExecutor().log("Creating indexes...");
closeOnEnd(Query.prepare("create index if not exists `uuid_1` on `whitelisted` (`uuid`)")).execute();
closeOnEnd(Query.prepare("create index if not exists `ip_1` on `responses` (`ip`)")).execute();
closeOnEnd(Query.prepare("create index if not exists `proxy_1` on `responses` (`proxy`)")).execute();
closeOnEnd(Query.prepare("create index if not exists `inserted_1` on `responses` (`inserted`)")).execute();
closeOnEnd(Query.prepare("create index if not exists `ip_1` on `whitelisted-ips` (`ip`)")).execute();
createIndexIfAbsent("whitelisted", "uuid_1", "`uuid`");
createIndexIfAbsent("responses", "ip_1", "`ip`");
createIndexIfAbsent("responses", "proxy_1", "`proxy`");
createIndexIfAbsent("responses", "inserted_1", "`inserted`");
createIndexIfAbsent("whitelisted-ips", "ip_1", "`ip`");
} catch (SQLException e) {
throw new DatabaseException("Failed to update database", e);
} finally {
@@ -69,6 +70,47 @@ public class First implements Version<VPNDatabase> {
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
public int versionNumber() {
return 0;
@@ -78,7 +120,7 @@ public class First implements Version<VPNDatabase> {
public boolean needsUpdate(VPNDatabase database) {
try(var statement = Query.prepare("select * from `database_version` where version = 0")) {
try(ResultSet set = statement.executeQuery()) {
return set.getFetchSize() == 0;
return !set.next();
}
} catch (SQLException e) {
return true;
@@ -20,6 +20,7 @@ import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.database.DatabaseException;
import dev.brighten.antivpn.database.VPNDatabase;
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.Query;
import dev.brighten.antivpn.database.version.Version;
@@ -31,12 +32,12 @@ import java.sql.SQLException;
import java.util.ArrayList;
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<>();
@Override
public void update(VPNDatabase database) throws DatabaseException {
if(database instanceof H2VPN h2VPN) {
if(database instanceof H2VPN h2VPN && !(database instanceof MySqlVPN)) {
h2VPN.backupDatabase();
}
List<String> whitelistedIps = new ArrayList<>();
@@ -58,7 +59,7 @@ public class Second implements Version<VPNDatabase> {
"ip_start BIGINT NOT NULL, " +
"ip_end BIGINT NOT NULL)"))
.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 -> {
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("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())).execute();
} catch (Throwable e) {
@@ -109,9 +110,7 @@ public class Second implements Version<VPNDatabase> {
private void rollback(List<String> ipAddresses) throws SQLException {
AntiVPN.getInstance().getExecutor().log("Rolling back to version 0...");
try(var statement = Query.prepare("DROP INDEX idx_ip_range ON `whitelisted-ranges`")) {
statement.execute();
}
dropIndexIfPresent("whitelisted-ranges", "idx_ip_range");
try(var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) {
statement.execute();
}
@@ -124,9 +123,7 @@ public class Second implements Version<VPNDatabase> {
statement.execute();
}
try(var statement = Query.prepare("create index if not exists `ip_1` on `whitelisted-ips` (`ip`)")) {
statement.execute();
}
createIndexIfAbsent("whitelisted-ips", "ip_1", "`ip`");
try(var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) {
statement.execute();
@@ -151,7 +148,7 @@ public class Second implements Version<VPNDatabase> {
public boolean needsUpdate(VPNDatabase database) {
try (var statement = Query.prepare("select * from `database_version` where version = 1")) {
try(var set = statement.executeQuery()) {
return set.getFetchSize() == 0;
return !set.next();
}
} catch (SQLException e) {
return true;
@@ -106,7 +106,7 @@ public class Third implements Version<VPNDatabase> {
public boolean needsUpdate(VPNDatabase database) {
try (var statement = Query.prepare("select * from `database_version` where version = 2")) {
try(var set = statement.executeQuery()) {
return set.getFetchSize() == 0;
return !set.next();
}
} catch (SQLException e) {
return true;
@@ -54,14 +54,11 @@ public class MySQL {
}
}
private static boolean didRetry = false;
public static void initH2() {
if(didRetry) {
AntiVPN.getInstance().getExecutor().log(Level.WARNING,
"Already attempted to retry H2 connection, skipping.");
return;
}
initH2(true);
}
private static void initH2(boolean allowRetry) {
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
if (!dataFolder.exists() && dataFolder.mkdirs()) {
AntiVPN.getInstance().getExecutor().log("Created database directory");
@@ -85,9 +82,13 @@ public class MySQL {
AntiVPN.getInstance().getExecutor()
.log("H2 database file is incompatible with this version of AntiVPN. " +
"Backing up old database file...");
backupOldDB(dbFile, dataFolder);
initH2();
didRetry = true;
shutdown();
if (allowRetry && backupOldDB(dbFile, dataFolder)) {
initH2(false);
} else {
AntiVPN.getInstance().getExecutor().log(
"Could not back up and remove the incompatible H2 database file automatically.");
}
} else {
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) {
if (dbFile.exists()) {
try {
// 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);
}
public static boolean backupOldDB(File dbFile, File dataFolder) {
if (!dbFile.exists()) {
return true;
}
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() {