diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java index 4075df510..78754ee53 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java @@ -160,7 +160,7 @@ public enum Dependency { "IydH+gkk2Iom36QrgSi2+hFAgC2AQSWJFZboyl8pEyI=", Relocation.of("postgresql", "org{}postgresql") ), - H2_DRIVER( + H2_DRIVER_LEGACY( "com.h2database", "h2", // seems to be a compat bug in 1.4.200 with older dbs @@ -170,6 +170,14 @@ public enum Dependency { // we don't apply relocations to h2 - it gets loaded via // an isolated classloader ), + H2_DRIVER( + "com.h2database", + "h2", + "2.1.214", + "1iPNwPYdIYz1SajQnxw5H/kQlhFrIuJHVHX85PvnK9A=" + // we don't apply relocations to h2 - it gets loaded via + // an isolated classloader + ), SQLITE_DRIVER( "org.xerial", "sqlite-jdbc", diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java index f705740c9..189a6105a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java @@ -128,6 +128,7 @@ public class DependencyRegistry { case ASM_COMMONS: case JAR_RELOCATOR: case H2_DRIVER: + case H2_DRIVER_LEGACY: case SQLITE_DRIVER: return false; default: diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/StorageFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/StorageFactory.java index d07787732..a59455699 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/StorageFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/StorageFactory.java @@ -114,7 +114,7 @@ public class StorageFactory { case H2: return new SqlStorage( this.plugin, - new H2ConnectionFactory(this.plugin.getBootstrap().getDataDirectory().resolve("luckperms-h2")), + new H2ConnectionFactory(this.plugin.getBootstrap().getDataDirectory().resolve("luckperms-h2-v2")), this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) ); case POSTGRESQL: diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/H2ConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/H2ConnectionFactory.java index ddae98fa8..49ff843f7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/H2ConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/H2ConnectionFactory.java @@ -30,9 +30,12 @@ import me.lucko.luckperms.common.dependencies.classloader.IsolatedClassLoader; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import java.lang.reflect.Constructor; +import java.nio.file.Files; import java.nio.file.Path; import java.sql.Connection; import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collections; import java.util.EnumSet; import java.util.Properties; import java.util.function.Function; @@ -56,16 +59,22 @@ public class H2ConnectionFactory extends FlatfileConnectionFactory { IsolatedClassLoader classLoader = plugin.getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.H2_DRIVER)); try { Class connectionClass = classLoader.loadClass("org.h2.jdbc.JdbcConnection"); - this.connectionConstructor = connectionClass.getConstructor(String.class, Properties.class); + this.connectionConstructor = connectionClass.getConstructor(String.class, Properties.class, String.class, Object.class, boolean.class); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } + + try { + new MigrateH2ToVersion2(plugin, super.getWriteFile().getParent()).run(this); + } catch (Exception e) { + plugin.getLogger().warn("Something went wrong whilst upgrading the LuckPerms database. Please report this on GitHub.", e); + } } @Override protected Connection createConnection(Path file) throws SQLException { try { - return (Connection) this.connectionConstructor.newInstance("jdbc:h2:" + file.toString(), new Properties()); + return (Connection) this.connectionConstructor.newInstance("jdbc:h2:" + file.toString(), new Properties(), null, null, false); } catch (ReflectiveOperationException e) { if (e.getCause() instanceof SQLException) { throw (SQLException) e.getCause(); @@ -83,6 +92,79 @@ public class H2ConnectionFactory extends FlatfileConnectionFactory { @Override public Function getStatementProcessor() { - return s -> s.replace('\'', '`').replace("LIKE", "ILIKE"); + return s -> s.replace('\'', '`') + .replace("LIKE", "ILIKE") + .replace("value", "`value`") + .replace("``value``", "`value`"); } + + /** + * Migrates the old (version 1) H2 database to version 2. + * + * See here for more info. + */ + private static final class MigrateH2ToVersion2 { + private final LuckPermsPlugin plugin; + private final Path directory; + + MigrateH2ToVersion2(LuckPermsPlugin plugin, Path directory) { + this.plugin = plugin; + this.directory = directory; + } + + public void run(H2ConnectionFactory newFactory) throws Exception { + Path oldDatabase = this.directory.resolve("luckperms-h2"); + Path oldDatabaseWriteFile = this.directory.resolve("luckperms-h2.mv.db"); + + if (!Files.exists(oldDatabaseWriteFile)) { + return; + } + + Path tempMigrationFile = this.directory.resolve("luckperms-h2-migration.sql"); + + this.plugin.getLogger().warn("[DB Upgrade] Found an old (v1) H2 database file. LuckPerms will now attempt to upgrade it to v2 (this is a one time operation)."); + + this.plugin.getLogger().info("[DB Upgrade] Stage 1: Exporting the old database to an intermediary file..."); + Constructor constructor = getConnectionConstructor(); + try (Connection c = getConnection(constructor, oldDatabase)) { + try (Statement stmt = c.createStatement()) { + stmt.execute(String.format("SCRIPT TO '%s'", tempMigrationFile)); + } + } + + this.plugin.getLogger().info("[DB Upgrade] Stage 2: Importing the intermediary file into the new database..."); + try (Connection c = newFactory.getConnection()) { + try (Statement stmt = c.createStatement()) { + stmt.execute(String.format("RUNSCRIPT FROM '%s'", tempMigrationFile)); + } + } + + this.plugin.getLogger().info("[DB Upgrade] Stage 3: Tidying up..."); + Files.deleteIfExists(tempMigrationFile); + Files.move(oldDatabaseWriteFile, this.directory.resolve("luckperms-h2-v1-backup.mv.db")); + + this.plugin.getLogger().info("[DB Upgrade] All done!"); + } + + private Constructor getConnectionConstructor() { + this.plugin.getDependencyManager().loadDependencies(Collections.singleton(Dependency.H2_DRIVER_LEGACY)); + IsolatedClassLoader classLoader = this.plugin.getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.H2_DRIVER_LEGACY)); + try { + Class connectionClass = classLoader.loadClass("org.h2.jdbc.JdbcConnection"); + return connectionClass.getConstructor(String.class, Properties.class); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private Connection getConnection(Constructor constructor, Path file) { + try { + return (Connection) constructor.newInstance("jdbc:h2:" + file.toString(), new Properties()); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } + + }