From f0ed71cc54f36fa5458d422f4846148fd99d9abf Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sun, 29 Oct 2023 23:18:57 +1300 Subject: [PATCH] Read idx migrations (index) files and support GraalVM native image, --- ebean-migration/pom.xml | 4 +- .../io/ebean/migration/MigrationConfig.java | 2 +- .../io/ebean/migration/MigrationVersion.java | 3 + .../runner/LocalMigrationResources.java | 53 +++++++++++ .../runner/LocalUriMigrationResource.java | 42 +++++++++ .../migration/runner/MigrationTable.java | 13 +-- .../resource-config.json | 10 +++ .../MigrationRunner_platform_Test.java | 3 +- .../ebean/migration/MigrationVersionTest.java | 12 +++ .../runner/MigrationTableAsyncTest.java | 2 +- .../MigrationTableCreateTableRaceTest.java | 2 +- test-native-image/pom.xml | 87 +++++++++++++++++++ .../src/main/java/org/example/Main.java | 30 +++++++ .../dbmigration/postgres/1.0__initial.sql | 12 +++ .../resources/dbmigration/postgres/1.1.sql | 5 ++ .../postgres/idx_postgres.migrations | 2 + .../example/StartPostgresContainerTest.java | 16 ++++ 17 files changed, 286 insertions(+), 12 deletions(-) create mode 100644 ebean-migration/src/main/java/io/ebean/migration/runner/LocalUriMigrationResource.java create mode 100644 ebean-migration/src/main/resources/META-INF/native-image/io.ebean.migration.ebean-migration/resource-config.json create mode 100644 test-native-image/pom.xml create mode 100644 test-native-image/src/main/java/org/example/Main.java create mode 100644 test-native-image/src/main/resources/dbmigration/postgres/1.0__initial.sql create mode 100644 test-native-image/src/main/resources/dbmigration/postgres/1.1.sql create mode 100644 test-native-image/src/main/resources/dbmigration/postgres/idx_postgres.migrations create mode 100644 test-native-image/src/test/java/org/example/StartPostgresContainerTest.java diff --git a/ebean-migration/pom.xml b/ebean-migration/pom.xml index ca51097..b70ab4d 100644 --- a/ebean-migration/pom.xml +++ b/ebean-migration/pom.xml @@ -135,8 +135,8 @@ mvn install:install-file -Dfile=/some/path/to/ojdbc7.jar -DgroupId=oracle \ io.ebean - ebean-test-docker - 5.4 + ebean-test-containers + 7.1 test diff --git a/ebean-migration/src/main/java/io/ebean/migration/MigrationConfig.java b/ebean-migration/src/main/java/io/ebean/migration/MigrationConfig.java index 2749b45..a972692 100644 --- a/ebean-migration/src/main/java/io/ebean/migration/MigrationConfig.java +++ b/ebean-migration/src/main/java/io/ebean/migration/MigrationConfig.java @@ -549,7 +549,7 @@ public void setName(String name) { * Return the base platform that was set. */ public String getBasePlatform() { - return basePlatform; + return basePlatform != null ? basePlatform : platform; } /** diff --git a/ebean-migration/src/main/java/io/ebean/migration/MigrationVersion.java b/ebean-migration/src/main/java/io/ebean/migration/MigrationVersion.java index b1c2e12..c04059c 100644 --- a/ebean-migration/src/main/java/io/ebean/migration/MigrationVersion.java +++ b/ebean-migration/src/main/java/io/ebean/migration/MigrationVersion.java @@ -190,6 +190,9 @@ public static String trim(String raw) { * Parse the raw version string into a MigrationVersion. */ public static MigrationVersion parse(String raw) { + if (raw.endsWith(".sql")) { + raw = raw.substring(0, raw.length() - 4); + } if (raw.startsWith("V") || raw.startsWith("v")) { raw = raw.substring(1); } diff --git a/ebean-migration/src/main/java/io/ebean/migration/runner/LocalMigrationResources.java b/ebean-migration/src/main/java/io/ebean/migration/runner/LocalMigrationResources.java index 694e0ef..4cc44be 100644 --- a/ebean-migration/src/main/java/io/ebean/migration/runner/LocalMigrationResources.java +++ b/ebean-migration/src/main/java/io/ebean/migration/runner/LocalMigrationResources.java @@ -6,6 +6,8 @@ import io.ebean.migration.MigrationConfig; import io.ebean.migration.MigrationVersion; +import java.io.*; +import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -45,9 +47,60 @@ boolean readInitResources() { * Read all the migration resources (SQL scripts) returning true if there are versions. */ boolean readResources() { + if (readFromIndex()) { + return true; + } return readResourcesForPath(migrationConfig.getMigrationPath()); } + private boolean readFromIndex() { + final var base = "/" + migrationConfig.getMigrationPath() + "/"; + final var basePlatform = migrationConfig.getBasePlatform(); + final var indexName = "idx_" + basePlatform + ".migrations"; + URL idx = resource(base + indexName); + if (idx != null) { + return loadFromIndexFile(idx, base); + } + idx = resource(base + basePlatform + '/' + indexName); + if (idx != null) { + return loadFromIndexFile(idx, base + basePlatform + '/'); + } + final var platform = migrationConfig.getPlatform(); + idx = resource(base + platform + indexName); + if (idx != null) { + return loadFromIndexFile(idx, base + platform + '/'); + } + return false; + } + + private URL resource(String base) { + return LocalMigrationResources.class.getResource(base); + } + + private boolean loadFromIndexFile(URL idx, String base) { + try (var reader = new LineNumberReader(new InputStreamReader(idx.openStream()))) { + String line; + while ((line = reader.readLine()) != null) { + if (!line.isEmpty()) { + final String[] pair = line.split(","); + if (pair.length == 2) { + final var checksum = Integer.parseInt(pair[0]); + final var location = pair[1].trim(); + final String substring = location.substring(0, location.length() - 4); + final var version = MigrationVersion.parse(substring); + final var url = resource(base + location); + versions.add(new LocalUriMigrationResource(version, location, url, checksum)); + } + } + } + + return !versions.isEmpty(); + + } catch (IOException e) { + throw new UncheckedIOException("Error reading idx file", e); + } + } + private boolean readResourcesForPath(String path) { // try to load from base platform first final String basePlatform = migrationConfig.getBasePlatform(); diff --git a/ebean-migration/src/main/java/io/ebean/migration/runner/LocalUriMigrationResource.java b/ebean-migration/src/main/java/io/ebean/migration/runner/LocalUriMigrationResource.java new file mode 100644 index 0000000..ffc6cb8 --- /dev/null +++ b/ebean-migration/src/main/java/io/ebean/migration/runner/LocalUriMigrationResource.java @@ -0,0 +1,42 @@ +package io.ebean.migration.runner; + +import io.ebean.migration.MigrationVersion; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.net.URL; + +/** + * A local URL based DB migration resource. + */ +final class LocalUriMigrationResource extends LocalMigrationResource { + + private final URL resource; + private final int checksum; + + LocalUriMigrationResource(MigrationVersion version, String location, URL resource, int checksum) { + super(version, location); + this.resource = resource; + this.checksum = checksum; + } + + public int checksum() { + return checksum; + } + + @Override + public String content() { + try (var reader = new InputStreamReader(resource.openStream())) { + var writer = new StringWriter(1024); + reader.transferTo(writer); + return writer.toString(); + } catch (IOException e) { + throw new IllegalStateException(missingOpensMessage(), e); + } + } + + private String missingOpensMessage() { + return "NPE reading DB migration content at [" + location + "] Probably missing an 'opens dbmigration;' in module-info.java"; + } +} diff --git a/ebean-migration/src/main/java/io/ebean/migration/runner/MigrationTable.java b/ebean-migration/src/main/java/io/ebean/migration/runner/MigrationTable.java index 934813a..1ea127f 100644 --- a/ebean-migration/src/main/java/io/ebean/migration/runner/MigrationTable.java +++ b/ebean-migration/src/main/java/io/ebean/migration/runner/MigrationTable.java @@ -296,7 +296,10 @@ private boolean shouldRun(LocalMigrationResource localVersion, LocalMigrationRes private boolean runMigration(LocalMigrationResource local, MigrationMetaRow existing) throws SQLException { String script = null; int checksum; - if (local instanceof LocalDdlMigrationResource) { + if (local instanceof LocalUriMigrationResource) { + script = convertScript(local.content()); + checksum = ((LocalUriMigrationResource)local).checksum(); + } else if (local instanceof LocalDdlMigrationResource) { script = convertScript(local.content()); checksum = Checksum.calculate(script); } else { @@ -402,13 +405,13 @@ private void executeMigration(LocalMigrationResource local, String script, int c private long executeMigration(LocalMigrationResource local, String script) throws SQLException { long start = System.currentTimeMillis(); - if (local instanceof LocalDdlMigrationResource) { - log.log(DEBUG, "run migration {0}", local.location()); - scriptRunner.runScript(script, "run migration version: " + local.version()); - } else { + if (local instanceof LocalJdbcMigrationResource) { JdbcMigration migration = ((LocalJdbcMigrationResource) local).migration(); log.log(INFO, "Executing jdbc migration version: {0} - {1}", local.version(), migration); migration.migrate(connection); + } else { + log.log(DEBUG, "run migration {0}", local.location()); + scriptRunner.runScript(script, "run migration version: " + local.version()); } executionCount++; return System.currentTimeMillis() - start; diff --git a/ebean-migration/src/main/resources/META-INF/native-image/io.ebean.migration.ebean-migration/resource-config.json b/ebean-migration/src/main/resources/META-INF/native-image/io.ebean.migration.ebean-migration/resource-config.json new file mode 100644 index 0000000..ae5a364 --- /dev/null +++ b/ebean-migration/src/main/resources/META-INF/native-image/io.ebean.migration.ebean-migration/resource-config.json @@ -0,0 +1,10 @@ +{ + "resources": [ + { + "pattern": ".*\\.sql" + }, + { + "pattern": ".*\\.migrations" + } + ] +} diff --git a/ebean-migration/src/test/java/io/ebean/migration/MigrationRunner_platform_Test.java b/ebean-migration/src/test/java/io/ebean/migration/MigrationRunner_platform_Test.java index e62c7f5..8f6e6a9 100644 --- a/ebean-migration/src/test/java/io/ebean/migration/MigrationRunner_platform_Test.java +++ b/ebean-migration/src/test/java/io/ebean/migration/MigrationRunner_platform_Test.java @@ -1,8 +1,7 @@ package io.ebean.migration; import io.ebean.ddlrunner.DdlRunner; -import io.ebean.docker.commands.*; -import io.ebean.docker.container.ContainerBuilderDb; +import io.ebean.test.containers.*; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; diff --git a/ebean-migration/src/test/java/io/ebean/migration/MigrationVersionTest.java b/ebean-migration/src/test/java/io/ebean/migration/MigrationVersionTest.java index 35f5674..ce65b8c 100644 --- a/ebean-migration/src/test/java/io/ebean/migration/MigrationVersionTest.java +++ b/ebean-migration/src/test/java/io/ebean/migration/MigrationVersionTest.java @@ -108,6 +108,18 @@ void test_parse_when_v_prefix() { assertThat(version.type()).isEqualTo("V"); } + @Test + void test_parse_when_sql_suffix() { + + MigrationVersion version = MigrationVersion.parse("v1_0__Foo.sql"); + assertThat(version.isRepeatable()).isFalse(); + assertThat(version.comment()).isEqualTo("Foo"); + assertThat(version.normalised()).isEqualTo("1.0"); + assertThat(version.asString()).isEqualTo("1_0"); + assertThat(version.raw()).isEqualTo("1_0__Foo"); + assertThat(version.type()).isEqualTo("V"); + } + @Test void repeatable_compareTo() { diff --git a/ebean-migration/src/test/java/io/ebean/migration/runner/MigrationTableAsyncTest.java b/ebean-migration/src/test/java/io/ebean/migration/runner/MigrationTableAsyncTest.java index 067fa14..51cf7e8 100644 --- a/ebean-migration/src/test/java/io/ebean/migration/runner/MigrationTableAsyncTest.java +++ b/ebean-migration/src/test/java/io/ebean/migration/runner/MigrationTableAsyncTest.java @@ -3,9 +3,9 @@ import io.ebean.datasource.DataSourceConfig; import io.ebean.datasource.DataSourceFactory; import io.ebean.datasource.DataSourcePool; -import io.ebean.docker.commands.*; import io.ebean.migration.MigrationConfig; import io.ebean.migration.MigrationRunner; +import io.ebean.test.containers.*; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; diff --git a/ebean-migration/src/test/java/io/ebean/migration/runner/MigrationTableCreateTableRaceTest.java b/ebean-migration/src/test/java/io/ebean/migration/runner/MigrationTableCreateTableRaceTest.java index 003bdb1..e42b647 100644 --- a/ebean-migration/src/test/java/io/ebean/migration/runner/MigrationTableCreateTableRaceTest.java +++ b/ebean-migration/src/test/java/io/ebean/migration/runner/MigrationTableCreateTableRaceTest.java @@ -3,8 +3,8 @@ import io.ebean.datasource.DataSourceConfig; import io.ebean.datasource.DataSourceFactory; import io.ebean.datasource.DataSourcePool; -import io.ebean.docker.commands.PostgresContainer; import io.ebean.migration.MigrationConfig; +import io.ebean.test.containers.PostgresContainer; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml new file mode 100644 index 0000000..c9be92e --- /dev/null +++ b/test-native-image/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + org.avaje + java11-oss + 3.10 + + + org.example + test-native-image + + + 21 + UTF-8 + 0.9.27 + org.example.Main + + + + + io.ebean + ebean-migration + 13.9.1-SNAPSHOT + + + + io.ebean + ebean-datasource + 8.0 + + + + org.postgresql + postgresql + 42.6.0 + + + + io.ebean + ebean-test-containers + 7.1 + test + + + + io.avaje + junit + 1.1 + test + + + + + + + native + + + + org.graalvm.buildtools + native-maven-plugin + ${version.plugin.nativeimage} + + + build-native + + build + + package + + + --no-fallback + -H:IncludeLocales=de,en + + + + + + + + + + + diff --git a/test-native-image/src/main/java/org/example/Main.java b/test-native-image/src/main/java/org/example/Main.java new file mode 100644 index 0000000..7c3a3bd --- /dev/null +++ b/test-native-image/src/main/java/org/example/Main.java @@ -0,0 +1,30 @@ +package org.example; + +import io.ebean.migration.*; + +import java.io.InputStream; +import java.util.List; +import java.util.Properties; + +public class Main { + + public static void main(String[] args) { + // InputStream is = Main.class.getResourceAsStream("/dbmigration/postgres/idx_postgres.migrations"); + // System.out.println("GOT is: " + is); + + MigrationConfig config = new MigrationConfig(); + config.setDbUsername("mig"); + config.setDbPassword("test"); + config.setDbUrl("jdbc:postgresql://localhost:6432/mig"); + config.setBasePlatform("postgres"); + config.setPlatform("postgres"); + config.setMigrationPath("dbmigration"); + + MigrationRunner runner = new MigrationRunner(config); + runner.run(); + + //System.out.println("state: " + runner.checkState()); + System.out.println("DONE"); + } + +} diff --git a/test-native-image/src/main/resources/dbmigration/postgres/1.0__initial.sql b/test-native-image/src/main/resources/dbmigration/postgres/1.0__initial.sql new file mode 100644 index 0000000..c31ebf0 --- /dev/null +++ b/test-native-image/src/main/resources/dbmigration/postgres/1.0__initial.sql @@ -0,0 +1,12 @@ +-- apply changes +create table foo ( + id integer generated by default as identity not null, + assoc_one varchar(255), + constraint pk_foo primary key (id) +); + +create table bar ( + id integer generated by default as identity not null, + something varchar(255), + constraint pk_bar primary key (id) +); diff --git a/test-native-image/src/main/resources/dbmigration/postgres/1.1.sql b/test-native-image/src/main/resources/dbmigration/postgres/1.1.sql new file mode 100644 index 0000000..bc4e714 --- /dev/null +++ b/test-native-image/src/main/resources/dbmigration/postgres/1.1.sql @@ -0,0 +1,5 @@ +create table baz ( + id integer generated by default as identity not null, + something varchar(255), + constraint pk_baz primary key (id) +); diff --git a/test-native-image/src/main/resources/dbmigration/postgres/idx_postgres.migrations b/test-native-image/src/main/resources/dbmigration/postgres/idx_postgres.migrations new file mode 100644 index 0000000..d7bd318 --- /dev/null +++ b/test-native-image/src/main/resources/dbmigration/postgres/idx_postgres.migrations @@ -0,0 +1,2 @@ +-745768926, 1.0__initial.sql +39858255, 1.1.sql diff --git a/test-native-image/src/test/java/org/example/StartPostgresContainerTest.java b/test-native-image/src/test/java/org/example/StartPostgresContainerTest.java new file mode 100644 index 0000000..01246da --- /dev/null +++ b/test-native-image/src/test/java/org/example/StartPostgresContainerTest.java @@ -0,0 +1,16 @@ +package org.example; + +import org.junit.jupiter.api.Test; +import io.ebean.test.containers.*; + +class StartPostgresContainerTest { + + @Test + void test() { + PostgresContainer.builder("15") + .port(6432) + .dbName("mig") + .build() + .start(); + } +}