From efdee053cce085faea5c3b21a24bc1c786c40e5d Mon Sep 17 00:00:00 2001 From: "robin.bygrave" Date: Wed, 10 Jun 2026 17:02:58 +1200 Subject: [PATCH] db-migration: Change I__ init scripts to NOT overwrite (allow customisation) This allows for customisation of the built in I__ init scripts. For the case of postgres db partitions a customisation to support unlogged tables was for example always being overwritten. This change makes the I__ scripts a "add if not already exists". Note that R__ repeatable scripts are "always overwrite" --- .../dbmigration/DefaultDbMigration.java | 15 ++++- .../dbmigration/DefaultDbMigrationTest.java | 55 +++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java index c44c6da5f..bcdfa8cca 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java @@ -397,12 +397,21 @@ public class DefaultDbMigration implements DbMigration { } /** - * Write (or override) the "repeatable" migration script. + * Write the "repeatable" or "init" migration script. + *

+ * Repeatable ({@code R__}) scripts are always (re)written from their source + * (typically {@code extra-ddl.xml} views), but an init ({@code I__}) script is + * only written when it does not already exist. Init scripts run once to + * bootstrap a database and can be hand-tuned after first generation. */ - private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException { + void writeExtraDdl(File migrationDir, DdlScript script) throws IOException { String fullName = repeatableMigrationName(script.isInit(), script.getName()); - logger.log(DEBUG, "writing repeatable script {0}", fullName); File file = new File(migrationDir, fullName); + if (script.isInit() && file.exists()) { + logger.log(DEBUG, "skip existing init script {0}", fullName); + return; + } + logger.log(DEBUG, "writing repeatable script {0}", fullName); try (Writer writer = IOUtils.newWriter(file)) { writer.write(script.getValue()); writer.flush(); diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DefaultDbMigrationTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DefaultDbMigrationTest.java index 5420ace62..9eb26e38c 100644 --- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DefaultDbMigrationTest.java +++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DefaultDbMigrationTest.java @@ -1,7 +1,15 @@ package io.ebeaninternal.dbmigration; +import io.ebeaninternal.extraddl.model.DdlScript; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -30,4 +38,51 @@ class DefaultDbMigrationTest { migration.checkDropVersion("1.0", "1.0.0"); migration.checkDropVersion("1.0", "1.1"); } + + @Test + void writeExtraDdl_initScript_notOverwrittenWhenExists(@TempDir Path dir) throws IOException { + File migrationDir = dir.toFile(); + + migration.writeExtraDdl(migrationDir, initScript("partition help", "ORIGINAL")); + File initFile = new File(migrationDir, "I__partition_help.sql"); + assertThat(initFile).exists().content().isEqualTo("ORIGINAL"); + + // hand-tune the generated init script + Files.writeString(initFile.toPath(), "CUSTOMISED"); + + // regeneration must not clobber an existing init script + migration.writeExtraDdl(migrationDir, initScript("partition help", "ORIGINAL")); + assertThat(initFile).content().isEqualTo("CUSTOMISED"); + } + + @Test + void writeExtraDdl_repeatableScript_alwaysRewritten(@TempDir Path dir) throws IOException { + File migrationDir = dir.toFile(); + + migration.writeExtraDdl(migrationDir, repeatableScript("my view", "V1")); + File file = new File(migrationDir, "R__my_view.sql"); + assertThat(file).exists().content().isEqualTo("V1"); + + Files.writeString(file.toPath(), "STALE"); + + // repeatable scripts are regenerated from their source every time + migration.writeExtraDdl(migrationDir, repeatableScript("my view", "V1")); + assertThat(file).content().isEqualTo("V1"); + } + + private static DdlScript initScript(String name, String value) { + return script(name, value, true); + } + + private static DdlScript repeatableScript(String name, String value) { + return script(name, value, false); + } + + private static DdlScript script(String name, String value, boolean init) { + DdlScript script = new DdlScript(); + script.setName(name); + script.setValue(value); + script.setInit(init); + return script; + } }