From be91e3f3f71dc3da0aa9e8d8baca86d392f3b7a7 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Thu, 24 Dec 2015 16:41:06 +1300 Subject: [PATCH] #506 - Changes to DB Migration, support run on startup and external version numbering --- .../avaje/ebean/config/DbMigrationConfig.java | 200 +++++++++++++++++- .../avaje/ebean/dbmigration/DbMigration.java | 117 +++++++--- .../migrationreader/MigrationXmlReader.java | 35 +-- .../dbmigration/model/MigrationModel.java | 99 +++------ .../dbmigration/model/MigrationResource.java | 51 +++++ .../dbmigration/model/MigrationVersion.java | 86 ++++++++ .../dbmigration/model/PlatformDdlWriter.java | 43 ++-- .../server/core/DefaultServer.java | 5 + .../dbmigration/model/MigrationModelTest.java | 39 ---- .../model/MigrationVersionTest.java | 47 ++++ 10 files changed, 566 insertions(+), 156 deletions(-) create mode 100644 src/main/java/com/avaje/ebean/dbmigration/model/MigrationResource.java create mode 100644 src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java delete mode 100644 src/test/java/com/avaje/ebean/dbmigration/model/MigrationModelTest.java create mode 100644 src/test/java/com/avaje/ebean/dbmigration/model/MigrationVersionTest.java diff --git a/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java b/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java index 3783ba087..95b7dd301 100644 --- a/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java +++ b/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java @@ -1,15 +1,75 @@ package com.avaje.ebean.config; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.config.dbplatform.DbPlatformName; +import com.avaje.ebean.dbmigration.DbMigration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Configuration for the DB migration processing. */ public class DbMigrationConfig { + protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class); + + /** + * The database platform to generate migration DDL for. + */ + protected DbPlatformName platform; + + protected boolean useSubdirectories; + + /** + * Set to true if the DB migration should be generated on server start. + */ + protected boolean generate; + + /** + * The migration version name (typically FlywayDb compatible). + *

+ * Example: 1.1.1_2 + *

+ * The version is expected to be the combination of the current pom version plus + * a 'feature' id. The combined version must be unique and ordered to work with + * FlywayDb so each developer sets a unique version so that the migration script + * generated is unique (typically just prior to being submitted as a merge request). + */ + protected String version; + + /** + * Description text that can be appended to the version to become the ddl script file name. + *

+ * So if the name is "a foo table" then the ddl script file could be: + * "1.1.1_2__a-foo-table.sql" + *

+ * When the DB migration relates to a git feature (merge request) then this description text + * is a short description of the feature. + */ + protected String name; + /** * Resource path for the migration xml and sql. * Typically you would change 'app' to be a better/more unique. */ - private String resourcePath = "dbmigration/app"; + protected String resourcePath = "dbmigration/app"; + + /** + * Return the DB platform to generate migration DDL for. + * + * We typically need to explicitly specify this as migration can often be generated + * when running against H2. + */ + public DbPlatformName getPlatform() { + return platform; + } + + /** + * Set the DB platform to generate migration DDL for. + */ + public void setPlatform(DbPlatformName platform) { + this.platform = platform; + } /** * Return the resource path for db migrations. @@ -18,6 +78,20 @@ public class DbMigrationConfig { return resourcePath; } + /** + * Return true if the 'rollback' and 'drop' scripts should be put into subdirectories. + */ + public boolean isUseSubdirectories() { + return useSubdirectories; + } + + /** + * Set to true if the 'rollback' and 'drop' scripts should be put into subdirectories. + */ + public void setUseSubdirectories(boolean useSubdirectories) { + this.useSubdirectories = useSubdirectories; + } + /** * Set the resource path for db migrations. *

@@ -30,10 +104,134 @@ public class DbMigrationConfig { this.resourcePath = resourcePath; } + /** + * Set the migration version. + *

+ * Note that version set via System property or environment variable ddl.migration.version takes precedence. + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * Set the migration name. + *

+ * Note that name set via System property or environment variable ddl.migration.name takes precedence. + */ + public void setName(String name) { + this.name = name; + } + /** * Load the settings from the PropertiesWrapper. */ public void loadSettings(PropertiesWrapper properties) { resourcePath = properties.get("migration.resourcePath", resourcePath); + platform = properties.getEnum(DbPlatformName.class, "migration.platform", platform); + generate = properties.getBoolean("migration.generate", generate); + version = properties.get("migration.version", version); + name = properties.get("migration.name", name); + useSubdirectories = properties.getBoolean("migration.useSubdirectories", useSubdirectories); } + + /** + * Return true if the migration should be generated. + *

+ * It is expected that when an environment variable ddl.migration.enabled + * is set to true then the DB migration will generate the migration DDL. + *

+ */ + public boolean isGenerateOnStart() { + + // environment properties take precedence + String envGenerate = readEnvironment("ddl.migration.generate"); + if (envGenerate != null) { + return "true".equalsIgnoreCase(envGenerate.trim()); + } + return generate; + } + + + /** + * Called by EbeanServer on start. + * + *

+ * If enabled this generates the migration xml and DDL scripts. + *

+ */ + public void generateOnStart(EbeanServer server) { + + if (isGenerateOnStart()) { + if (platform == null) { + logger.warn("No platform set for migration DDL generation"); + } else { + // generate the migration xml and platform specific DDL + DbMigration migration = new DbMigration(server); + migration.setPlatform(platform); + try { + migration.generateMigration(); + } catch (Exception e) { + throw new RuntimeException("Error generating DB migration", e); + } + } + } + } + + /** + * Return the migration version (typically FlywayDb compatible). + *

+ * Example: 1.1.1_2 + *

+ * The version is expected to be the combination of the current pom version plus + * a 'feature' id. The combined version must be unique and ordered to work with + * FlywayDb so each developer sets a unique version so that the migration script + * generated is unique (typically just prior to being submitted as a merge request). + */ + public String getVersion() { + String envVersion = readEnvironment("ddl.migration.version"); + if (!isEmpty(envVersion)) { + return envVersion.trim(); + } + return version; + } + + /** + * Return the migration name which is short description text that can be appended to + * the migration version to become the ddl script file name. + *

+ * So if the name is "a foo table" then the ddl script file could be: + * "1.1.1_2__a-foo-table.sql" + *

+ *

+ * When the DB migration relates to a git feature (merge request) then this description text + * is a short description of the feature. + *

+ */ + public String getName() { + String envName = readEnvironment("ddl.migration.name"); + if (!isEmpty(envName)) { + return envName.trim(); + } + return name; + } + + /** + * Return the system or environment property. + */ + protected String readEnvironment(String key) { + + String val = System.getProperty(key); + if (val == null) { + val = System.getenv(key); + } + return val; + } + + /** + * Return true if the string is null or empty. + */ + protected boolean isEmpty(String val) { + return val == null || val.trim().isEmpty(); + } + } diff --git a/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java b/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java index 805a176c0..257cb00b2 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java +++ b/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java @@ -21,8 +21,8 @@ import com.avaje.ebean.dbmigration.model.CurrentModel; import com.avaje.ebean.dbmigration.model.MConfiguration; import com.avaje.ebean.dbmigration.model.MigrationModel; import com.avaje.ebean.dbmigration.model.ModelContainer; -import com.avaje.ebean.dbmigration.model.PlatformDdlWriter; import com.avaje.ebean.dbmigration.model.ModelDiff; +import com.avaje.ebean.dbmigration.model.PlatformDdlWriter; import com.avaje.ebeaninternal.api.SpiEbeanServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,6 +57,13 @@ public class DbMigration { protected static final Logger logger = LoggerFactory.getLogger(DbMigration.class); + private static final String initialVersion = "1.0"; + + /** + * Set to true if DbMigration run with online EbeanServer instance. + */ + protected final boolean online; + protected SpiEbeanServer server; protected DbMigrationConfig migrationConfig; @@ -71,7 +78,19 @@ public class DbMigration { protected DbConstraintNaming constraintNaming; + /** + * Create for offline migration generation. + */ public DbMigration() { + this.online = false; + } + + /** + * Create using online EbeanServer. + */ + public DbMigration(EbeanServer server) { + this.online = true; + setServer(server); } /** @@ -125,7 +144,9 @@ public class DbMigration { */ public void setPlatform(DatabasePlatform databasePlatform) { this.databasePlatform = databasePlatform; - DbOffline.setPlatform(databasePlatform.getName()); + if (!online) { + DbOffline.setPlatform(databasePlatform.getName()); + } } /** @@ -175,16 +196,18 @@ public class DbMigration { public void generateMigration() throws IOException { // use this flag to stop other plugins like full DDL generation - DbOffline.setRunningMigration(); + if (!online) { + DbOffline.setRunningMigration(); + } setDefaults(); try { - MigrationModel migrationModel = new MigrationModel(migrationConfig.getResourcePath()); - ModelContainer migrated = migrationModel.read(); - int nextMajorVersion = migrationModel.getNextMajorVersion(); - logger.info("next migration version {}", nextMajorVersion); + File migrationDirectory = getMigrationDirectory(); + + MigrationModel migrationModel = new MigrationModel(migrationDirectory); + ModelContainer migrated = migrationModel.read(); CurrentModel currentModel = new CurrentModel(server, constraintNaming); ModelContainer current = currentModel.read(); @@ -200,46 +223,88 @@ public class DbMigration { // there were actually changes to write Migration dbMigration = diff.getMigration(); - File writePath = getWritePath(); - logger.info("migration writing version {} to {}", nextMajorVersion, writePath.getAbsolutePath()); - writeMigrationXml(dbMigration, writePath, nextMajorVersion); + String fullVersion = getFullVersion(migrationModel); - if (databasePlatform != null) { - // writer needs the current model to provide table/column details for - // history ddl generation (triggers, history tables etc) - DdlWrite write = new DdlWrite(new MConfiguration(), currentModel.read()); - PlatformDdlWriter writer = new PlatformDdlWriter(databasePlatform, serverConfig); - writer.processMigration(dbMigration, write, writePath, nextMajorVersion); + logger.info("generating migration:{}", fullVersion); + if (!writeMigrationXml(dbMigration, migrationDirectory, fullVersion)) { + logger.warn("migration already exists, not generating DDL"); + + } else { + if (databasePlatform != null) { + // writer needs the current model to provide table/column details for + // history ddl generation (triggers, history tables etc) + DdlWrite write = new DdlWrite(new MConfiguration(), currentModel.read()); + PlatformDdlWriter writer = createDdlWriter(databasePlatform, ""); + writer.processMigration(dbMigration, write, migrationDirectory, fullVersion); + } + writeExtraPlatformDdl(fullVersion, currentModel, dbMigration, migrationDirectory); } - writeExtraPlatformDdl(nextMajorVersion, currentModel, dbMigration, writePath); - } finally { - DbOffline.reset(); + if (!online) { + DbOffline.reset(); + } } } + /** + * Return the full version for the migration being generated. + */ + private String getFullVersion(MigrationModel migrationModel) { + + String version = migrationConfig.getVersion(); + if (version == null) { + version = migrationModel.getNextVersion(initialVersion); + } + + String fullVersion = version; + + String name = migrationConfig.getName(); + if (name != null) { + fullVersion += "__" + toUnderScore(name); + } + return fullVersion; + } + + /** + * Replace spaces with underscores. + */ + private String toUnderScore(String name) { + return name.replace(' ','_'); + } + /** * Write any extra platform ddl. */ - protected void writeExtraPlatformDdl(int nextMajorVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException { + protected void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException { for (Pair pair : platforms) { DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read()); - - PlatformDdlWriter platformWriter = new PlatformDdlWriter(pair.platform, serverConfig, pair.prefix); - platformWriter.processMigration(dbMigration, platformBuffer, writePath, nextMajorVersion); + PlatformDdlWriter platformWriter = createDdlWriter(pair); + platformWriter.processMigration(dbMigration, platformBuffer, writePath, fullVersion); } } + private PlatformDdlWriter createDdlWriter(Pair pair) { + return createDdlWriter(pair.platform, pair.prefix); + } + + private PlatformDdlWriter createDdlWriter(DatabasePlatform platform, String prefix) { + return new PlatformDdlWriter(platform, serverConfig, prefix, migrationConfig.isUseSubdirectories()); + } + /** * Write the migration xml. */ - protected void writeMigrationXml(Migration dbMigration, File resourcePath, int migrationVersion) { + protected boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) { - File file = new File(resourcePath, "v"+migrationVersion+".0.xml"); + File file = new File(resourcePath, fullVersion+".xml"); + if (file.exists()) { + return false; + } MigrationXmlWriter xmlWriter = new MigrationXmlWriter(); xmlWriter.write(dbMigration, file); + return true; } /** @@ -260,7 +325,7 @@ public class DbMigration { /** * Return the file path to write the xml and sql to. */ - protected File getWritePath() { + protected File getMigrationDirectory() { // path to src/main/resources in typical maven project File resourceRootDir = new File(pathToResources); diff --git a/src/main/java/com/avaje/ebean/dbmigration/migrationreader/MigrationXmlReader.java b/src/main/java/com/avaje/ebean/dbmigration/migrationreader/MigrationXmlReader.java index 95d4df445..5fce3ba11 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/migrationreader/MigrationXmlReader.java +++ b/src/main/java/com/avaje/ebean/dbmigration/migrationreader/MigrationXmlReader.java @@ -6,6 +6,9 @@ import com.avaje.ebean.dbmigration.migration.Migration; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; /** @@ -15,19 +18,6 @@ public class MigrationXmlReader { private static final MigrationXmlReader INSTANCE = new MigrationXmlReader(); - /** - * Read and return a Migration from an xml document at the given resource path. - */ - public static Migration readMaybe(String resourcePath) { - - InputStream is = MigrationXmlReader.class.getResourceAsStream(resourcePath); - if (is == null) { - return null; - } - - return INSTANCE.read(is); - } - /** * Read and return a Migration from an xml document at the given resource path. */ @@ -41,10 +31,27 @@ public class MigrationXmlReader { return INSTANCE.read(is); } + /** + * Read and return a Migration from a migration xml file. + */ + public static Migration read(File migrationFile) { + + try { + FileInputStream is = new FileInputStream(migrationFile); + try { + return read(is); + } finally { + is.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + /** * Read and return a Migration from an xml document. */ - public Migration read(InputStream is) { + public static Migration read(InputStream is) { try { JAXBContext jaxbContext = JAXBContext.newInstance(Migration.class); diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/MigrationModel.java b/src/main/java/com/avaje/ebean/dbmigration/model/MigrationModel.java index cd555a71a..aacdde839 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/model/MigrationModel.java +++ b/src/main/java/com/avaje/ebean/dbmigration/model/MigrationModel.java @@ -1,12 +1,13 @@ package com.avaje.ebean.dbmigration.model; -import com.avaje.ebean.dbmigration.migration.Migration; -import com.avaje.ebean.dbmigration.migrationreader.MigrationXmlReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.LinkedHashSet; -import java.util.Set; +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Build the model from the series of migrations. @@ -17,26 +18,12 @@ public class MigrationModel { private final ModelContainer model = new ModelContainer(); - private final Set readVersions = new LinkedHashSet(); + private final File migrationDirectory; - private final String resourcePath; + private MigrationVersion lastVersion; - int nextMajorVersion; - - public MigrationModel(String resourcePath) { - this.resourcePath = normaliseResourcePath(resourcePath); - } - - private String normaliseResourcePath(String resourcePath) { - if (resourcePath.endsWith("/")) { - // trim trailing slash - resourcePath = resourcePath.substring(0, resourcePath.length()-1); - } - if (resourcePath.startsWith("/")) { - // trim leading slash - resourcePath = resourcePath.substring(1); - } - return resourcePath; + public MigrationModel(File migrationDirectory) { + this.migrationDirectory = migrationDirectory; } /** @@ -46,57 +33,41 @@ public class MigrationModel { public ModelContainer read() { readMigrations(); - logger.info("read versions {}", readVersions); return model; } - /** - * Return the set of versions that were read. - */ - public Set getReadVersions() { - return readVersions; - } - - public int getNextMajorVersion() { - return nextMajorVersion; - } - private void readMigrations() { - for (int majorVersion = 1; majorVersion < 100; majorVersion++) { - if (!readMinorVersions(majorVersion)){ - // no major.0 version so stopping - nextMajorVersion = majorVersion; - return; + // find all the migration xml files + File[] xmlFiles = migrationDirectory.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.getName().toLowerCase().endsWith(".xml"); } + }); + + List resources = new ArrayList(); + + for (File xmlFile: xmlFiles) { + resources.add(new MigrationResource(xmlFile)); + } + + // sort into version order before applying + Collections.sort(resources); + + for (MigrationResource migrationResource: resources) { + logger.debug("read {}", migrationResource); + model.apply(migrationResource.read()); + } + + // remember the last version + if (!resources.isEmpty()) { + lastVersion = resources.get(resources.size() - 1).getVersion(); } } - private boolean readMinorVersions(int majorVersion) { + public String getNextVersion(String initialVersion) { - for (int minorVersion = 0; minorVersion < 100; minorVersion++) { - if (!readMigration(majorVersion, minorVersion)) { - // continue reading next major if minorVersion 0 was read - return (minorVersion > 0); - } - } - return true; + return lastVersion == null ? initialVersion : lastVersion.nextVersion(); } - - private boolean readMigration(int majorVersion, int minorVersion) { - - String version = majorVersion+"."+minorVersion; - String path = "/"+resourcePath+"/v"+version+".xml"; - - Migration migration = MigrationXmlReader.readMaybe(path); - if (migration == null) { - logger.debug("... no migration at path:{}", path); - return false; - } - readVersions.add(version); - logger.trace("... read migration v{}", version); - model.apply(migration); - return true; - } - } diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/MigrationResource.java b/src/main/java/com/avaje/ebean/dbmigration/model/MigrationResource.java new file mode 100644 index 000000000..15f62aaf8 --- /dev/null +++ b/src/main/java/com/avaje/ebean/dbmigration/model/MigrationResource.java @@ -0,0 +1,51 @@ +package com.avaje.ebean.dbmigration.model; + +import com.avaje.ebean.dbmigration.migration.Migration; +import com.avaje.ebean.dbmigration.migrationreader.MigrationXmlReader; + +import java.io.File; + +/** + * Migration XML resource that holds the changes to be applied. + */ +public class MigrationResource implements Comparable { + + private final File migrationFile; + + private final MigrationVersion version; + + /** + * Construct with a migration xml file. + */ + public MigrationResource(File migrationFile) { + this.migrationFile = migrationFile; + this.version = MigrationVersion.parse(migrationFile.getName()); + } + + public String toString() { + return migrationFile.getName(); + } + + /** + * Return the version associated with this resource. + */ + public MigrationVersion getVersion() { + return version; + } + + /** + * Read and return the migration from the resource. + */ + public Migration read() { + + return MigrationXmlReader.read(migrationFile); + } + + /** + * Compare by underlying version. + */ + @Override + public int compareTo(MigrationResource other) { + return version.compareTo(other.version); + } +} diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java b/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java new file mode 100644 index 000000000..8bb5f5ed9 --- /dev/null +++ b/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java @@ -0,0 +1,86 @@ +package com.avaje.ebean.dbmigration.model; + +/** + * The version of a migration used so that migrations are processed in order. + */ +public class MigrationVersion implements Comparable { + + /** + * The raw version text. + */ + private final String raw; + + /** + * The ordering parts. + */ + private final int[] ordering; + + private MigrationVersion(String raw, int[] ordering) { + this.raw = raw; + this.ordering = ordering; + } + + public String toString() { + return raw; + } + + + public String nextVersion() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < ordering.length; i++) { + if (i < ordering.length -1 ) { + sb.append(ordering[i]).append("."); + } else { + sb.append(ordering[i]+1); + } + } + return sb.toString(); + } + + @Override + public int compareTo(MigrationVersion other) { + + int otherLength = other.ordering.length; + for (int i = 0; i < ordering.length; i++) { + if (i >= otherLength) { + // considered greater + return 1; + } + if (ordering[i] != other.ordering[i]) { + return (ordering[i] > other.ordering[i]) ? 1 : -1; + } + } + // considered the same + return 0; + } + + /** + * Parse the raw version string into a MigrationVersion. + */ + public static MigrationVersion parse(String raw) { + + String value = raw.replace("__","."); + value = value.replace('_','.'); + + String[] sections = value.split("\\."); + + int[] ordering = new int[sections.length]; + + int stopIndex = 0; + for (int i = 0; i < sections.length; i++) { + try { + ordering[i] = Integer.parseInt(sections[i]); + stopIndex++; + } catch (NumberFormatException e) { + // stop parsing + break; + } + } + + int[] actualOrder = new int[stopIndex]; + System.arraycopy(ordering, 0, actualOrder, 0, stopIndex); + + return new MigrationVersion(raw, actualOrder); + } + +} diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/PlatformDdlWriter.java b/src/main/java/com/avaje/ebean/dbmigration/model/PlatformDdlWriter.java index 3bb9a2b18..4f8cb0ffd 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/model/PlatformDdlWriter.java +++ b/src/main/java/com/avaje/ebean/dbmigration/model/PlatformDdlWriter.java @@ -24,20 +24,19 @@ public class PlatformDdlWriter { private final String platformPrefix; - public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig) { - this(platform, serverConfig, ""); - } + private final boolean useSubdirectories; - public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, String platformPrefix) { + public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, String platformPrefix, boolean useSubdirectories) { this.platform = platform; this.serverConfig = serverConfig; this.platformPrefix = platformPrefix; + this.useSubdirectories = useSubdirectories; } /** * Write the migration as platform specific ddl. */ - public void processMigration(Migration dbMigration, DdlWrite write, File writePath, int nextMajorVersion) throws IOException { + public void processMigration(Migration dbMigration, DdlWrite write, File writePath, String fullVersion) throws IOException { DdlHandler handler = handler(); @@ -49,16 +48,16 @@ public class PlatformDdlWriter { } handler.generateExtra(write); - writePlatformDdl(write, writePath, nextMajorVersion); + writePlatformDdl(write, writePath, fullVersion); } /** * Write the ddl files. */ - protected void writePlatformDdl(DdlWrite write, File resourcePath, int migrationVersion) throws IOException { + protected void writePlatformDdl(DdlWrite write, File resourcePath, String fullVersion) throws IOException { if (!write.isApplyEmpty()) { - FileWriter applyWriter = createWriter(resourcePath, migrationVersion, "apply.sql"); + FileWriter applyWriter = createWriter(resourcePath, fullVersion, ""); try { writeApplyDdl(applyWriter, write); applyWriter.flush(); @@ -67,7 +66,7 @@ public class PlatformDdlWriter { } if (!write.isApplyRollbackEmpty()) { - FileWriter applyRollbackWriter = createWriter(resourcePath, migrationVersion, "applyRollback.sql"); + FileWriter applyRollbackWriter = createWriter(resourcePath, fullVersion, "rollback"); try { writeApplyRollbackDdl(applyRollbackWriter, write); applyRollbackWriter.flush(); @@ -78,7 +77,7 @@ public class PlatformDdlWriter { } if (!write.isDropEmpty()) { - FileWriter dropWriter = createWriter(resourcePath, migrationVersion, "drop.sql"); + FileWriter dropWriter = createWriter(resourcePath, fullVersion, "drop"); try { writeDropDdl(dropWriter, write); dropWriter.flush(); @@ -88,12 +87,32 @@ public class PlatformDdlWriter { } } - protected FileWriter createWriter(File resourcePath, int migrationVersion, String suffix) throws IOException { + protected FileWriter createWriter(File path, String fullVersion, String suffix) throws IOException { - File applyFile = new File(resourcePath, "v" + migrationVersion + ".0-" + platformPrefix + suffix); + String fileName = fullVersion; + if (!platformPrefix.isEmpty()) { + fileName += "-"+platformPrefix; + } + if (!suffix.isEmpty()) { + fileName += "-"+suffix; + path = subPath(path, suffix); + } + fileName += ".sql"; + File applyFile = new File(path, fileName); return new FileWriter(applyFile); } + protected File subPath(File path, String suffix) { + if (!useSubdirectories) { + return path; + } + File subPath = new File(path, suffix); + if (!subPath.exists()) { + subPath.mkdirs(); + } + return subPath; + } + /** * Write the 'Apply' DDL buffers to the writer. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index da07b5bf9..7c660add5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -9,6 +9,7 @@ import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebean.bean.PersistenceContext.WithOption; import com.avaje.ebean.cache.ServerCacheManager; +import com.avaje.ebean.config.DbMigrationConfig; import com.avaje.ebean.config.EncryptKeyManager; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.DatabasePlatform; @@ -373,6 +374,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Start any services after registering with the ClusterManager. */ public void start() { + DbMigrationConfig migrationConfig = serverConfig.getMigrationConfig(); + if (migrationConfig != null) { + migrationConfig.generateOnStart(this); + } } /** diff --git a/src/test/java/com/avaje/ebean/dbmigration/model/MigrationModelTest.java b/src/test/java/com/avaje/ebean/dbmigration/model/MigrationModelTest.java deleted file mode 100644 index 1cc725e62..000000000 --- a/src/test/java/com/avaje/ebean/dbmigration/model/MigrationModelTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.avaje.ebean.dbmigration.model; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.*; - -public class MigrationModelTest { - - @Test - public void testRead() throws Exception { - - MigrationModel migrationModel = new MigrationModel("dbmigration/app1"); - ModelContainer model = migrationModel.read(); - - assertThat(migrationModel.getReadVersions()).contains("1.0","1.1","2.0"); - assertThat(model.getTable("v10_table")).isNotNull(); - } - - @Test - public void testRead_leadingSlash() throws Exception { - - MigrationModel migrationModel = new MigrationModel("/dbmigration/app1"); - ModelContainer model = migrationModel.read(); - - assertThat(migrationModel.getReadVersions()).contains("1.0","1.1","2.0"); - assertThat(model.getTable("v10_table")).isNotNull(); - } - - @Test - public void testRead_trailingSlash() throws Exception { - - MigrationModel migrationModel = new MigrationModel("/dbmigration/app1/"); - ModelContainer model = migrationModel.read(); - - assertThat(migrationModel.getReadVersions()).contains("1.0","1.1","2.0"); - assertThat(model.getTable("v10_table")).isNotNull(); - } - -} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/dbmigration/model/MigrationVersionTest.java b/src/test/java/com/avaje/ebean/dbmigration/model/MigrationVersionTest.java new file mode 100644 index 000000000..214bf41db --- /dev/null +++ b/src/test/java/com/avaje/ebean/dbmigration/model/MigrationVersionTest.java @@ -0,0 +1,47 @@ +package com.avaje.ebean.dbmigration.model; + +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class MigrationVersionTest { + + @Test + public void testParse() throws Exception { + + MigrationVersion v0 = MigrationVersion.parse("1.1.1_2__Foo"); + MigrationVersion v1 = MigrationVersion.parse("1.1.1.2_junk"); + MigrationVersion v2 = MigrationVersion.parse("1.1_1.2_foo"); + + assertThat(v0.compareTo(v1)).isEqualTo(0); + assertThat(v1.compareTo(v0)).isEqualTo(0); + assertThat(v1.compareTo(v2)).isEqualTo(0); + + } + + @Test + public void testNextVersion() { + + assertThat(MigrationVersion.parse("2").nextVersion()).isEqualTo("3"); + assertThat(MigrationVersion.parse("1.0").nextVersion()).isEqualTo("1.1"); + assertThat(MigrationVersion.parse("2.0.b34").nextVersion()).isEqualTo("2.1"); + assertThat(MigrationVersion.parse("1.1.1_2__Foo").nextVersion()).isEqualTo("1.1.1.3"); + assertThat(MigrationVersion.parse("1.1.1.2_junk").nextVersion()).isEqualTo("1.1.1.3"); + } + + @Test + public void testCompareTo() throws Exception { + + MigrationVersion v1 = MigrationVersion.parse("1.1.1.2_junk"); + MigrationVersion v2 = MigrationVersion.parse("2.1_1.2_junk"); + MigrationVersion v3 = MigrationVersion.parse("1.2_1.2_junk"); + MigrationVersion v4 = MigrationVersion.parse("1.1_1.3_junk"); + MigrationVersion v5 = MigrationVersion.parse("1.1.1.1_junk"); + + assertThat(v1.compareTo(v2)).isEqualTo(-1); + assertThat(v1.compareTo(v3)).isEqualTo(-1); + assertThat(v1.compareTo(v4)).isEqualTo(-1); + + assertThat(v1.compareTo(v5)).isEqualTo(1); + } +} \ No newline at end of file