Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generate flyway migrations from hibernate update scripts #43817

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,73 @@
package io.quarkus.flyway.test;

import java.io.File;
import java.nio.file.Files;
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);
rmanibus marked this conversation as resolved.
Show resolved Hide resolved

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