Skip to content

Commit

Permalink
generate flyway migrations from hibernate update scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
rmanibus committed Oct 28, 2024
1 parent fcc7889 commit 148d6ba
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkus.agroal.spi;

import java.util.function.Supplier;

import io.quarkus.builder.item.MultiBuildItem;

public final class JdbcUpdateSQLGeneratorBuildItem extends MultiBuildItem {

final String databaseName;
final Supplier<String> sqlSupplier;

public JdbcUpdateSQLGeneratorBuildItem(String databaseName, Supplier<String> sqlSupplier) {
this.databaseName = databaseName;
this.sqlSupplier = sqlSupplier;
}

public String getDatabaseName() {
return databaseName;
}

public Supplier<String> getSqlSupplier() {
return sqlSupplier;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.function.Supplier;

import io.quarkus.agroal.spi.JdbcInitialSQLGeneratorBuildItem;
import io.quarkus.agroal.spi.JdbcUpdateSQLGeneratorBuildItem;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
Expand All @@ -24,17 +25,23 @@ public class FlywayDevUIProcessor {
@BuildStep(onlyIf = IsDevelopment.class)
@Record(value = RUNTIME_INIT, optional = true)
CardPageBuildItem create(FlywayDevUIRecorder recorder, FlywayBuildTimeConfig buildTimeConfig,
List<JdbcInitialSQLGeneratorBuildItem> generatorBuildItem,
List<JdbcInitialSQLGeneratorBuildItem> createGeneratorBuildItem,
List<JdbcUpdateSQLGeneratorBuildItem> updateGeneratorBuildItem,
CurateOutcomeBuildItem curateOutcomeBuildItem) {

Map<String, Supplier<String>> initialSqlSuppliers = new HashMap<>();
for (JdbcInitialSQLGeneratorBuildItem buildItem : generatorBuildItem) {
for (JdbcInitialSQLGeneratorBuildItem buildItem : createGeneratorBuildItem) {
initialSqlSuppliers.put(buildItem.getDatabaseName(), buildItem.getSqlSupplier());
}

Map<String, Supplier<String>> updateSqlSuppliers = new HashMap<>();
for (JdbcUpdateSQLGeneratorBuildItem buildItem : updateGeneratorBuildItem) {
updateSqlSuppliers.put(buildItem.getDatabaseName(), buildItem.getSqlSupplier());
}

String artifactId = curateOutcomeBuildItem.getApplicationModel().getAppArtifact().getArtifactId();

recorder.setInitialSqlSuppliers(initialSqlSuppliers, artifactId);
recorder.setSqlSuppliers(initialSqlSuppliers, updateSqlSuppliers, artifactId);

CardPageBuildItem card = new CardPageBuildItem();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export class QwcFlywayDatasources extends QwcHotReloadElement {

_actionRenderer(ds) {
return html`${this._renderMigrationButtons(ds)}
${this._renderUpdateButton(ds)}
${this._renderCreateButton(ds)}`;
}

Expand All @@ -90,7 +91,16 @@ export class QwcFlywayDatasources extends QwcHotReloadElement {
</vaadin-button>`;
}
}


_renderUpdateButton(ds) {
if(ds.hasMigrations){
return html`
<vaadin-button theme="small" @click=${() => this._showCreateDialog(ds)} class="button" title="Create update migration file. Always manually review the created file as it can cause data loss">
<vaadin-icon icon="font-awesome-solid:plus"></vaadin-icon> Generate Migration File
</vaadin-button>`;
}
}

_renderCreateButton(ds) {
if(ds.createPossible){
return html`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.quarkus.flyway.test;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.fasterxml.jackson.databind.JsonNode;

import io.quarkus.dev.console.DevConsoleManager;
import io.quarkus.devui.tests.DevUIJsonRPCTest;
import io.quarkus.test.QuarkusDevModeTest;

public class FlywayDevModeUpdateFromHibernateTest extends DevUIJsonRPCTest {

public FlywayDevModeUpdateFromHibernateTest() {
super("io.quarkus.quarkus-flyway");
}

@RegisterExtension
static final QuarkusDevModeTest config = new QuarkusDevModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(FlywayDevModeUpdateFromHibernateTest.class, Fruit.class)
.addAsResource(new StringAsset(
" create sequence Fruit_SEQ start with 1 increment by 50;\n" +
"\n" +
" create table Fruit (\n" +
" id integer not null,\n" +
" primary key (id)\n" +
" );"),
"db/update/V2.0.0__Quarkus.sql")
.addAsResource(new StringAsset(
"quarkus.flyway.migrate-at-start=true\nquarkus.flyway.locations=db/update"),
"application.properties"));

@Test
public void testGenerateMigrationFromHibernate() throws Exception {

Map<String, Object> params = Map.of("ds", "<default>");
JsonNode devuiresponse = super.executeJsonRPCMethod("update", params);

Assertions.assertNotNull(devuiresponse);
String type = devuiresponse.get("type").asText();
Assertions.assertNotNull(type);
Assertions.assertEquals("success", type);

File migrationsDir = DevConsoleManager.getHotReplacementContext().getResourcesDir().get(0).resolve("db/update")
.toFile();
File[] newMigrations = migrationsDir.listFiles((dir, name) -> !name.equals("V2.0.0__Quarkus.sql"));
Assertions.assertNotNull(newMigrations);
Assertions.assertEquals(1, newMigrations.length);
Assertions.assertTrue(newMigrations[0].getName().startsWith("V2."));

String content = Files.readString(newMigrations[0].toPath());

Assertions.assertEquals("\n" +
" alter table if exists Fruit \n" +
" add column name varchar(40);\n" +
"\n" +
" alter table if exists Fruit \n" +
" drop constraint if exists UKqn1mp5t3oovyl0h02glapi2iv;\n" +
"\n" +
" alter table if exists Fruit \n" +
" add constraint UKqn1mp5t3oovyl0h02glapi2iv unique (name);\n", content);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
@Recorder
public class FlywayDevUIRecorder {

public RuntimeValue<Boolean> setInitialSqlSuppliers(Map<String, Supplier<String>> initialSqlSuppliers, String artifactId) {
public RuntimeValue<Boolean> setSqlSuppliers(Map<String, Supplier<String>> initialSqlSuppliers,
Map<String, Supplier<String>> updateSuppliers, String artifactId) {
FlywayJsonRpcService rpcService = Arc.container().instance(FlywayJsonRpcService.class).get();
rpcService.setInitialSqlSuppliers(initialSqlSuppliers);
rpcService.setUpdateSqlSuppliers(updateSuppliers);
rpcService.setArtifactId(artifactId);
return new RuntimeValue<>(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import static java.util.List.of;

import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
Expand All @@ -24,6 +27,7 @@
public class FlywayJsonRpcService {

private Map<String, Supplier<String>> initialSqlSuppliers;
private Map<String, Supplier<String>> updateSqlSuppliers;
private String artifactId;
private Map<String, FlywayDatasource> datasources;

Expand All @@ -34,6 +38,10 @@ public void setInitialSqlSuppliers(Map<String, Supplier<String>> initialSqlSuppl
this.initialSqlSuppliers = initialSqlSuppliers;
}

public void setUpdateSqlSuppliers(Map<String, Supplier<String>> updateSqlSuppliers) {
this.updateSqlSuppliers = updateSqlSuppliers;
}

public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}
Expand Down Expand Up @@ -161,6 +169,50 @@ public FlywayActionResponse create(String ds) {
return errorNoDatasource(ds);
}

public FlywayActionResponse update(String ds) {
try {
Supplier<String> found = updateSqlSuppliers.get(ds);
if (found == null) {
return new FlywayActionResponse("error", "Unable to find SQL Update generator");
}
String script = found.get();
if (script == null) {
return new FlywayActionResponse("error", "Missing Flyway update script for [" + ds + "]");
}
Flyway flyway = getFlyway(ds);
if (flyway == null) {
return errorNoDatasource(ds);
}
if (locations.isEmpty()) {
return new FlywayActionResponse("error", "Datasource has no locations configured");
}

List<Path> resourcesDir = DevConsoleManager.getHotReplacementContext().getResourcesDir();
if (resourcesDir.isEmpty()) {
return new FlywayActionResponse("error", "No resource directory found");
}
// In the current project only
Path path = resourcesDir.get(0);

Path migrationDir = path.resolve(locations.get(0));
Files.createDirectories(migrationDir);
SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd.HHmmss");
String timestamp = format.format(new Timestamp(System.currentTimeMillis()));
BigInteger major = flyway.info().current().isVersioned() ? flyway.info().current().getVersion().getMajor()
: BigInteger.ONE;
Path file = migrationDir.resolve(
"V" + major + "." + timestamp + "__" + artifactId + ".sql");

Files.writeString(file, script);

return new FlywayActionResponse("success",
"migration created");

} catch (Throwable t) {
return new FlywayActionResponse("error", t.getMessage());
}
}

public int getNumberOfDatasources() {
Collection<FlywayContainer> flywayContainers = new FlywayContainersSupplier().get();
return flywayContainers.size();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;

import io.quarkus.agroal.spi.JdbcInitialSQLGeneratorBuildItem;
import io.quarkus.agroal.spi.JdbcUpdateSQLGeneratorBuildItem;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.deployment.IsDevelopment;
Expand All @@ -16,6 +17,7 @@
import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem;
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
import io.quarkus.hibernate.orm.runtime.dev.HibernateOrmDevInfoCreateDDLSupplier;
import io.quarkus.hibernate.orm.runtime.dev.HibernateOrmDevInfoUpdateDDLSupplier;
import io.quarkus.hibernate.orm.runtime.dev.HibernateOrmDevJsonRpcService;

@BuildSteps(onlyIf = { HibernateOrmEnabled.class, IsDevelopment.class })
Expand Down Expand Up @@ -69,4 +71,15 @@ void handleInitialSql(List<PersistenceUnitDescriptorBuildItem> persistenceUnitDe
}
}

@BuildStep
void handleUpdateSql(List<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptorBuildItems,
BuildProducer<JdbcUpdateSQLGeneratorBuildItem> updateSQLGeneratorBuildItemBuildProducer) {
for (PersistenceUnitDescriptorBuildItem puDescriptor : persistenceUnitDescriptorBuildItems) {
String puName = puDescriptor.getPersistenceUnitName();
String dsName = puDescriptor.getConfig().getDataSource().orElse(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME);
updateSQLGeneratorBuildItemBuildProducer
.produce(new JdbcUpdateSQLGeneratorBuildItem(dsName, new HibernateOrmDevInfoUpdateDDLSupplier(puName)));
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.hibernate.orm.runtime.dev;

import java.util.Collection;
import java.util.Objects;
import java.util.function.Supplier;

import io.quarkus.runtime.annotations.RecordableConstructor;

public class HibernateOrmDevInfoUpdateDDLSupplier implements Supplier<String> {

private final String puName;

@RecordableConstructor
public HibernateOrmDevInfoUpdateDDLSupplier(String puName) {
this.puName = puName;
}

@Override
public String get() {
Collection<HibernateOrmDevInfo.PersistenceUnit> persistenceUnits = HibernateOrmDevController.get()
.getInfo().getPersistenceUnits();
for (var p : persistenceUnits) {
if (Objects.equals(puName, p.getName())) {
return p.getUpdateDDL();
}
}
return null;
}

public String getPuName() {
return puName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ public void handle(AsyncResult<Void> event) {
});
}


private Buffer createBuffer(ByteBuf data) {
return new NoBoundChecksBuffer(data);
}
Expand Down

0 comments on commit 148d6ba

Please sign in to comment.