diff --git a/pom.xml b/pom.xml index adb177e6d..6ff89a8e1 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,12 @@ 2.1.1 + + org.avaje + avaje-dbmigration + 1.1.1 + + org.slf4j slf4j-api diff --git a/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java b/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java index ffa07076a..abfa940ea 100644 --- a/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java +++ b/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java @@ -3,6 +3,8 @@ 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.avaje.dbmigration.MigrationConfig; +import org.avaje.dbmigration.MigrationRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -494,4 +496,20 @@ public class DbMigrationConfig { return val == null || val.trim().isEmpty(); } + /** + * Create the MigrationRunner to run migrations if necessary. + */ + public MigrationRunner createRunner(ClassLoader classLoader) { + + MigrationConfig runnerConfig = new MigrationConfig(); + runnerConfig.setMetaTable(metaTable); + runnerConfig.setApplySuffix(applySuffix); + runnerConfig.setMigrationPath(migrationPath); + runnerConfig.setRunPlaceholderMap(runPlaceholderMap); + runnerConfig.setRunPlaceholders(runPlaceholders); + runnerConfig.setDbUsername(getDbUser()); + runnerConfig.setDbPassword(getDbPassword()); + runnerConfig.setClassLoader(classLoader); + return new MigrationRunner(runnerConfig); + } } diff --git a/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java b/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java index 58cee566f..4b9e12073 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java +++ b/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java @@ -5,6 +5,7 @@ import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.dbmigration.model.CurrentModel; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.extraddl.model.ExtraDdlXmlReader; +import org.avaje.dbmigration.ddl.DdlRunner; import javax.persistence.PersistenceException; import java.io.File; diff --git a/src/main/java/com/avaje/ebean/dbmigration/DdlParser.java b/src/main/java/com/avaje/ebean/dbmigration/DdlParser.java deleted file mode 100644 index c0f787d7e..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/DdlParser.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.avaje.ebean.dbmigration; - -import javax.persistence.PersistenceException; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.List; - -/** - * Parses string content into separate SQL/DDL statements. - */ -public class DdlParser { - - /** - * Break up the sql in reader into a list of statements using the semi-colon and $$ delimiters; - */ - public List parse(StringReader reader) { - - try { - BufferedReader br = new BufferedReader(reader); - StatementsSeparator statements = new StatementsSeparator(); - - String s; - while ((s = br.readLine()) != null) { - s = s.trim(); - statements.nextLine(s); - } - - return statements.statements; - - } catch (IOException e) { - throw new PersistenceException(e); - } - } - - - /** - * Local utility used to detect the end of statements / separate statements. - * This is often just the semicolon character but for trigger/procedures this - * detects the $$ demarcation used in the history DDL generation for MySql and - * Postgres. - */ - static class StatementsSeparator { - - ArrayList statements = new ArrayList(); - - boolean trimDelimiter; - - boolean inDbProcedure; - - StringBuilder sb = new StringBuilder(); - - void lineContainsDollars(String line) { - if (inDbProcedure) { - if (trimDelimiter) { - line = line.replace("$$",""); - } - endOfStatement(line); - } else { - // MySql style delimiter needs to be trimmed/removed - trimDelimiter = line.equals("delimiter $$"); - if (!trimDelimiter) { - sb.append(line).append(" "); - } - } - inDbProcedure = !inDbProcedure; - } - - void endOfStatement(String line) { - // end of Db procedure - sb.append(line); - statements.add(sb.toString().trim()); - sb = new StringBuilder(); - } - - void nextLine(String line) { - - if (line.contains("$$")) { - lineContainsDollars(line); - return; - } - - if (sb.length() == 0 && (line.isEmpty() || line.startsWith("--"))) { - // ignore leading empty lines and sql comments - return; - } - - if (inDbProcedure) { - sb.append(line).append(" "); - return; - } - - int semiPos = line.indexOf(';'); - if (semiPos == -1) { - sb.append(line).append(" "); - - } else if (semiPos == line.length() - 1) { - // semicolon at end of line - endOfStatement(line); - - } else { - // semicolon in middle of line - String preSemi = line.substring(0, semiPos); - endOfStatement(preSemi); - sb.append(line.substring(semiPos + 1)); - } - } - } -} diff --git a/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java b/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java deleted file mode 100644 index 30eb26a0b..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.avaje.ebean.dbmigration; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.StringReader; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -/** - * Runs DDL scripts. - */ -public class DdlRunner { - - protected static final Logger logger = LoggerFactory.getLogger(DdlRunner.class); - - private DdlParser ddlParser = new DdlParser(); - - private final String scriptName; - - private final boolean expectErrors; - - /** - * Construct with a script name (for logging) and flag indicating if errors are expected. - */ - public DdlRunner(boolean expectErrors, String scriptName) { - this.expectErrors = expectErrors; - this.scriptName = scriptName; - } - - /** - * Parse the content into sql statements and execute them in a transaction. - */ - public int runAll(String content, Connection connection) throws SQLException { - - List statements = ddlParser.parse(new StringReader(content)); - return runStatements(statements, connection); - } - - /** - * Execute all the statements in a single transaction. - */ - private int runStatements(List statements, Connection connection) throws SQLException { - - List noDuplicates = new ArrayList(); - - for (String statement : statements) { - if (!noDuplicates.contains(statement)) { - noDuplicates.add(statement); - } - } - - logger.info("Executing {} - {} statements", scriptName, noDuplicates.size()); - - for (int i = 0; i < noDuplicates.size(); i++) { - String xOfy = (i + 1) + " of " + noDuplicates.size(); - runStatement(expectErrors, xOfy, noDuplicates.get(i), connection); - } - - return noDuplicates.size(); - } - - /** - * Execute the statement. - */ - private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) throws SQLException { - - PreparedStatement pstmt = null; - try { - - // trim and remove trailing ; or / - stmt = stmt.trim(); - if (stmt.endsWith(";")) { - stmt = stmt.substring(0, stmt.length() - 1); - } else if (stmt.endsWith("/")) { - stmt = stmt.substring(0, stmt.length() - 1); - } - - if (logger.isDebugEnabled()) { - logger.debug("executing " + oneOf + " " + getSummary(stmt)); - } - - pstmt = c.prepareStatement(stmt); - pstmt.execute(); - - } catch (SQLException e) { - if (expectErrors) { - logger.debug(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage()); - } else { - String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]"; - throw new SQLException(msg, e); - } - - } finally { - if (pstmt != null) { - try { - pstmt.close(); - } catch (SQLException e) { - logger.error("Error closing pstmt", e); - } - } - } - } - - private String getSummary(String s) { - if (s.length() > 80) { - return s.substring(0, 80).trim() + "..."; - } - return s; - } - -} diff --git a/src/main/java/com/avaje/ebean/dbmigration/MigrationRunner.java b/src/main/java/com/avaje/ebean/dbmigration/MigrationRunner.java deleted file mode 100644 index a4a217dee..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/MigrationRunner.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.avaje.ebean.dbmigration; - -import com.avaje.ebean.EbeanServer; -import com.avaje.ebean.config.DbMigrationConfig; -import com.avaje.ebean.config.ServerConfig; -import com.avaje.ebean.dbmigration.runner.LocalMigrationResource; -import com.avaje.ebean.dbmigration.runner.LocalMigrationResources; -import com.avaje.ebean.dbmigration.runner.MigrationTable; -import com.avaje.ebeaninternal.util.JdbcClose; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.sql.DataSource; -import java.io.IOException; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.List; - -/** - * Runs the DB migration typically on application start. - */ -public class MigrationRunner { - - public static final Logger logger = LoggerFactory.getLogger("org.avaje.ebean.DbMigration"); - - private final EbeanServer server; - - private final ServerConfig config; - - private final DbMigrationConfig migrationConfig; - - public MigrationRunner(EbeanServer server, DbMigrationConfig migrationConfig) { - this.server = server; - this.config = server.getPluginApi().getServerConfig(); - this.migrationConfig = migrationConfig; - } - - /** - * Run the migrations if there are any that need running. - */ - public void run() { - - LocalMigrationResources resources = new LocalMigrationResources(config, migrationConfig); - if (!resources.readResources()) { - logger.debug("no migrations to check"); - return; - } - - String migrationUser = migrationConfig.getDbUser(); - String migrationPwd = migrationConfig.getDbPassword(); - if (migrationUser == null) { - throw new IllegalStateException("No DB migration user specified (to run the db migration) ?"); - } - - DataSource dataSource = server.getPluginApi().getDataSource(); - if (dataSource == null) { - throw new IllegalStateException("No dataSource when trying to run migration? " - +"Maybe trying to generate DBMigration when ebean.migration.run=true is set? " - +"Perhaps need to set ebean.migration.run=false in test-ebean.properties?"); - } - - Connection connection; - try { - connection = dataSource.getConnection(migrationUser, migrationPwd); - } catch (SQLException e) { - throw new IllegalArgumentException("Error trying to connect to database using DB Migration user [" + migrationUser + "]", e); - } - - try { - logger.debug("using db user [{}] to run migrations ..."); - connection.setAutoCommit(false); - runMigrations(resources, connection); - - connection.commit(); - - } catch (Exception e) { - JdbcClose.rollback(connection); - throw new RuntimeException(e); - - } finally { - JdbcClose.close(connection); - } - } - - /** - * Run all the migrations as needed. - */ - private void runMigrations(LocalMigrationResources resources, Connection connection) throws SQLException, IOException { - - MigrationTable table = new MigrationTable(server, migrationConfig, connection); - table.createIfNeeded(); - - // get the migrations in version order - List localVersions = resources.getVersions(); - - logger.info("local migrations:{} existing migrations:{}", localVersions.size(), table.size()); - - LocalMigrationResource priorVersion = null; - - // run migrations in order - for (int i = 0; i < localVersions.size(); i++) { - LocalMigrationResource localVersion = localVersions.get(i); - if (!table.shouldRun(localVersion, priorVersion)) { - break; - } - priorVersion = localVersion; - connection.commit(); - } - } - -} diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/Checksum.java b/src/main/java/com/avaje/ebean/dbmigration/runner/Checksum.java deleted file mode 100644 index 515bf3606..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/runner/Checksum.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.StringReader; -import java.util.zip.CRC32; - -/** - * Calculates the checksum for the given string content. - */ -public class Checksum { - - /** - * Returns the checksum of this string. - */ - public static int calculate(String str) { - - final CRC32 crc32 = new CRC32(); - - BufferedReader bufferedReader = new BufferedReader(new StringReader(str)); - try { - String line; - while ((line = bufferedReader.readLine()) != null) { - crc32.update(line.getBytes("UTF-8")); - } - } catch (IOException e) { - throw new RuntimeException("Failed to calculate checksum", e); - } - - return (int) crc32.getValue(); - } -} diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java b/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java deleted file mode 100644 index a2d71aebc..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import com.avaje.ebean.dbmigration.model.MigrationVersion; -import org.avaje.classpath.scanner.Resource; - -/** - * A DB migration resource (DDL script with version). - */ -public class LocalMigrationResource implements Comparable { - - /** - * Code for repeatable migrations. - */ - private static final String REPEAT_TYPE = "R"; - - /** - * Code for version migrations. - */ - private static final String VERSION_TYPE = "V"; - - private final MigrationVersion version; - - private final String location; - - private final Resource resource; - - /** - * Construct with version and resource. - */ - public LocalMigrationResource(MigrationVersion version, String location, Resource resource) { - this.version = version; - this.location = location; - this.resource = resource; - } - - public String toString() { - return version.toString(); - } - - /** - * Return true if the underlying version is "repeatable". - */ - public boolean isRepeatable() { - return version.isRepeatable(); - } - - /** - * Return the "key" that identifies the migration. - */ - public String key() { - if (isRepeatable()) { - return version.getComment().toLowerCase(); - } else { - return version.normalised(); - } - } - - /** - * Return the migration comment. - */ - public String getComment() { - String comment = version.getComment(); - return (comment == null || comment.isEmpty()) ? "-" : comment; - } - - /** - * Default ordering by version. - */ - @Override - public int compareTo(LocalMigrationResource o) { - return version.compareTo(o.version); - } - - /** - * Return the underlying migration version. - */ - public MigrationVersion getVersion() { - return version; - } - - /** - * Return the resource location. - */ - public String getLocation() { - return location; - } - - /** - * Return the content for the migration apply ddl script. - */ - public String getContent() { - return resource.loadAsString("UTF-8"); - } - - /** - * Return the type code ("R" or "V") for this migration. - */ - public String getType() { - return isRepeatable() ? REPEAT_TYPE : VERSION_TYPE; - } -} diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResources.java b/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResources.java deleted file mode 100644 index f7c3ee4f1..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResources.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import com.avaje.ebean.config.DbMigrationConfig; -import com.avaje.ebean.config.ServerConfig; -import com.avaje.ebean.dbmigration.model.MigrationVersion; -import org.avaje.classpath.scanner.Resource; -import org.avaje.classpath.scanner.ResourceFilter; -import org.avaje.classpath.scanner.Scanner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Loads the DB migration resources and sorts them into execution order. - */ -public class LocalMigrationResources { - - private static final Logger logger = LoggerFactory.getLogger(LocalMigrationResources.class); - - private final ServerConfig serverConfig; - - private final DbMigrationConfig migrationConfig; - - private final List versions = new ArrayList(); - - /** - * Construct with configuration options. - */ - public LocalMigrationResources(ServerConfig serverConfig, DbMigrationConfig migrationConfig) { - this.serverConfig = serverConfig; - this.migrationConfig = migrationConfig; - } - - /** - * Read all the migration resources (SQL scripts) returning true if there are versions. - */ - public boolean readResources() { - - String migrationPath = migrationConfig.getMigrationPath(); - - ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader(); - - Scanner scanner = new Scanner(classLoader); - List resourceList = scanner.scanForResources(migrationPath, new Match(migrationConfig)); - - logger.debug("resources: {}", resourceList); - - for (Resource resource : resourceList) { - String filename = resource.getFilename(); - if (filename.endsWith(migrationConfig.getApplySuffix())) { - int pos = filename.lastIndexOf(migrationConfig.getApplySuffix()); - String mainName = filename.substring(0, pos); - - MigrationVersion migrationVersion = MigrationVersion.parse(mainName); - LocalMigrationResource res = new LocalMigrationResource(migrationVersion, resource.getLocation(), resource); - versions.add(res); - } - } - - Collections.sort(versions); - return !versions.isEmpty(); - } - - /** - * Return the list of migration resources in version order. - */ - public List getVersions() { - return versions; - } - - - /** - * Filter used to find the migration scripts. - */ - private static class Match implements ResourceFilter { - - private final DbMigrationConfig migrationConfig; - - Match(DbMigrationConfig migrationConfig) { - this.migrationConfig = migrationConfig; - } - - @Override - public boolean isMatch(String name) { - return name.endsWith(migrationConfig.getApplySuffix()); - } - } -} diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationMetaRow.java b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationMetaRow.java deleted file mode 100644 index 2da2556b5..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationMetaRow.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import com.avaje.ebean.SqlRow; -import com.avaje.ebean.SqlUpdate; - -import java.sql.Timestamp; - -/** - * Bean holding migration execution details stored in the migration table. - */ -class MigrationMetaRow { - - private int id; - - private String type; - - private String version; - - private String comment; - - private int checksum; - - private Timestamp runOn; - - private String runBy; - - private long runTime; - - /** - * Construct for inserting into table. - */ - MigrationMetaRow(int id, String type, String version, String comment, int checksum, String runBy, Timestamp runOn, long runTime) { - this.id = id; - this.type = type; - this.version = version; - this.checksum = checksum; - this.comment = comment; - this.runBy = runBy; - this.runOn = runOn; - this.runTime = runTime; - } - - /** - * Construct from the SqlRow (read from table). - */ - MigrationMetaRow(SqlRow row) { - id = row.getInteger("id"); - type = row.getString("mtype"); - version = row.getString("mversion"); - comment = row.getString("mcomment"); - checksum = row.getInteger("mchecksum"); - runBy = row.getString("run_by"); - runTime = row.getLong("run_time"); - } - - public String toString() { - return "id:" + id + " type:" + type + " runVersion:" + version + " comment:" + comment + " runOn:" + runOn + " runBy:" + runBy; - } - - /** - * Return the id for this migration. - */ - int getId() { - return id; - } - - /** - * Return the normalised version for this migration. - */ - String getVersion() { - return version; - } - - /** - * Return the checksum for this migration. - */ - int getChecksum() { - return checksum; - } - - /** - * Bind to the insert statement. - */ - void bindInsert(SqlUpdate insert) { - insert.setParameter(1, id); - insert.setParameter(2, type); - insert.setParameter(3, "SUCCESS"); - insert.setParameter(4, version); - insert.setParameter(5, comment); - insert.setParameter(6, checksum); - insert.setParameter(7, runOn); - insert.setParameter(8, runBy); - insert.setParameter(9, runTime); - } - - /** - * Return the SQL insert given the table migration meta data is stored in. - */ - static String insertSql(String table) { - return "insert into " + table - + " (id, mtype, mstatus, mversion, mcomment, mchecksum, run_on, run_by, run_time)" - + " values (?,?,?,?,?,?,?,?,?)"; - } - -} diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationScriptRunner.java b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationScriptRunner.java deleted file mode 100644 index a2e0c6da5..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationScriptRunner.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import com.avaje.ebean.dbmigration.DdlRunner; - -import java.sql.Connection; -import java.sql.SQLException; - -/** - * Runs the DDL migration scripts. - */ -public class MigrationScriptRunner { - - private final Connection connection; - - /** - * Construct with a given connection. - */ - public MigrationScriptRunner(Connection connection) { - this.connection = connection; - } - - /** - * Execute all the DDL statements in the script. - */ - int runScript(boolean expectErrors, String content, String scriptName) throws SQLException { - - DdlRunner runner = new DdlRunner(expectErrors, scriptName); - return runner.runAll(content, connection); - } -} diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationTable.java b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationTable.java deleted file mode 100644 index a1c620507..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationTable.java +++ /dev/null @@ -1,263 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import com.avaje.ebean.EbeanServer; -import com.avaje.ebean.SqlQuery; -import com.avaje.ebean.SqlRow; -import com.avaje.ebean.SqlUpdate; -import com.avaje.ebean.config.DbMigrationConfig; -import com.avaje.ebean.config.ServerConfig; -import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebean.dbmigration.MigrationRunner; -import com.avaje.ebean.plugin.SpiServer; -import com.avaje.ebeaninternal.server.transaction.ExternalJdbcTransaction; -import com.avaje.ebeaninternal.util.IOUtils; -import org.slf4j.Logger; - -import java.io.IOException; -import java.net.URL; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.util.Enumeration; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Manages the migration table. - */ -public class MigrationTable { - - private static final Logger logger = MigrationRunner.logger; - - private final Connection connection; - - private final EbeanServer server; - - private final DatabasePlatform databasePlatform; - - private final String catalog; - private final String schema; - private final String table; - private final ServerConfig serverConfig; - private final String envUserName; - - private final Timestamp runOn = new Timestamp(System.currentTimeMillis()); - - private final ScriptTransform scriptTransform; - - private final String insertSql; - - private final LinkedHashMap migrations; - - private MigrationMetaRow lastMigration; - - /** - * Construct with server, configuration and jdbc connection (DB admin user). - */ - public MigrationTable(EbeanServer server, DbMigrationConfig migrationConfig, Connection connection) { - this.connection = connection; - this.server = server; - this.migrations = new LinkedHashMap(); - - SpiServer pluginApi = server.getPluginApi(); - this.serverConfig = pluginApi.getServerConfig(); - this.databasePlatform = pluginApi.getDatabasePlatform(); - this.catalog = null; - this.schema = null; - this.table = migrationConfig.getMetaTable(); - this.insertSql = MigrationMetaRow.insertSql(table); - this.scriptTransform = createScriptTransform(migrationConfig); - this.envUserName = System.getProperty("user.name"); - } - - /** - * Return the number of migrations in the DB migration table. - */ - public int size() { - return migrations.size(); - } - - /** - * Create the ScriptTransform for placeholder key/value replacement. - */ - private ScriptTransform createScriptTransform(DbMigrationConfig config) { - - Map map = PlaceholderBuilder.build(config.getRunPlaceholders(), config.getRunPlaceholderMap()); - return new ScriptTransform(map); - } - - /** - * Create the table is it does not exist. - */ - public void createIfNeeded() throws SQLException, IOException { - - if (!tableExists(connection)) { - createTable(connection); - } - - ExternalJdbcTransaction t = new ExternalJdbcTransaction(connection); - SqlQuery sqlQuery = server.createSqlQuery("select * from " + table + " order by id for update"); - List metaRows = server.findList(sqlQuery, t); - - for (SqlRow row : metaRows) { - MigrationMetaRow metaRow = new MigrationMetaRow(row); - addMigration(metaRow.getVersion(), metaRow); - } - } - - - private void createTable(Connection connection) throws IOException, SQLException { - - String script = ScriptTransform.table(table, getCreateTableScript()); - - MigrationScriptRunner run = new MigrationScriptRunner(connection); - run.runScript(false, script, "create migration table"); - } - - /** - * Return the create table script. - */ - private String getCreateTableScript() throws IOException { - // supply a script to override the default table create script - String script = readResource("migration-support/create-table.sql"); - if (script == null) { - // no, just use the default script - script = readResource("migration-support/default-create-table.sql"); - } - return script; - } - - private String readResource(String location) throws IOException { - - Enumeration resources = serverConfig.getClassLoadConfig().getResources(location); - if (resources.hasMoreElements()) { - URL url = resources.nextElement(); - return IOUtils.readUtf8(url.openStream()); - } - return null; - } - - /** - * Return true if the table exists. - */ - private boolean tableExists(Connection connection) throws SQLException { - boolean exists = databasePlatform.tableExists(connection, catalog, schema, table); - if (!exists) { - exists = databasePlatform.tableExists(connection, catalog, schema, table.toUpperCase()); - } - return exists; - } - - /** - * Return true if the migration ran successfully and false if the migration failed. - */ - public boolean shouldRun(LocalMigrationResource localVersion, LocalMigrationResource priorVersion) throws SQLException { - - if (priorVersion != null && !localVersion.isRepeatable()) { - if (!migrationExists(priorVersion)) { - logger.error("Migration {} requires prior migration {} which has not been run", localVersion.getVersion(), priorVersion.getVersion()); - return false; - } - } - - MigrationMetaRow existing = migrations.get(localVersion.key()); - return runMigration(localVersion, existing); - } - - /** - * Run the migration script. - * - * @param local The local migration resource - * @param existing The information for this migration existing in the table - * - * @return True if the migrations should continue - */ - private boolean runMigration(LocalMigrationResource local, MigrationMetaRow existing) throws SQLException { - - String script = convertScript(local.getContent()); - int checksum = Checksum.calculate(script); - - if (existing != null) { - - boolean matchChecksum = (existing.getChecksum() == checksum); - - if (!local.isRepeatable()) { - if (!matchChecksum) { - logger.error("Checksum mismatch on migration {}", local.getLocation()); - } - return true; - - } else if (matchChecksum) { - logger.trace("... skip unchanged repeatable migration {}", local.getLocation()); - return true; - } - } - - runMigration(local, script, checksum); - return true; - } - - /** - * Run a migration script as new migration or update on existing repeatable migration. - */ - private void runMigration(LocalMigrationResource local, String script, int checksum) throws SQLException { - - logger.debug("run migration {}", local.getLocation()); - - long start = System.currentTimeMillis(); - MigrationScriptRunner run = new MigrationScriptRunner(connection); - run.runScript(false, script, "run migration version: " + local.getVersion()); - - long exeMillis = System.currentTimeMillis() - start; - // insert new migration row - SqlUpdate insert = server.createSqlUpdate(insertSql); - MigrationMetaRow metaRow = createMetaRow(local, checksum, exeMillis); - metaRow.bindInsert(insert); - server.execute(insert, new ExternalJdbcTransaction(connection)); - - addMigration(local.key(), metaRow); - } - - /** - * Create the MigrationMetaRow for this migration. - */ - private MigrationMetaRow createMetaRow(LocalMigrationResource migration, int checksum, long exeMillis) { - - int nextId = 1; - if (lastMigration != null) { - nextId = lastMigration.getId() + 1; - } - - String type = migration.getType(); - String runVersion = migration.key(); - String comment = migration.getComment(); - - return new MigrationMetaRow(nextId, type, runVersion, comment, checksum, envUserName, runOn, exeMillis); - } - - /** - * Return true if the migration exists. - */ - private boolean migrationExists(LocalMigrationResource priorVersion) { - return migrations.containsKey(priorVersion.key()); - } - - /** - * Apply the placeholder key/value replacement on the script. - */ - private String convertScript(String script) { - return scriptTransform.transform(script); - } - - /** - * Register the successfully executed migration (to allow dependant scripts to run). - */ - private void addMigration(String key, MigrationMetaRow metaRow) { - lastMigration = metaRow; - if (metaRow.getVersion() == null) { - throw new IllegalStateException("No runVersion in db migration table row? " + metaRow); - } - migrations.put(key, metaRow); - } -} diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilder.java b/src/main/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilder.java deleted file mode 100644 index eec825394..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilder.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import java.util.HashMap; -import java.util.Map; - -/** - * Joins placeholder map and comma/equals delimited string. - */ -class PlaceholderBuilder { - - private final Map map = new HashMap(); - - /** - * Create with raw comma and equals delimited pairs plus map of key value pairs. - */ - public static Map build(String commaDelimited, Map placeholders) { - - PlaceholderBuilder builder = new PlaceholderBuilder(); - builder.add(commaDelimited); - builder.add(placeholders); - - return builder.map; - } - - private PlaceholderBuilder() { - - } - - /** - * Add a comma and equals delimited string to parse for key value pairs. - */ - public void add(String commaDelimited) { - - if (commaDelimited != null) { - String[] split = commaDelimited.split("[,;]"); - for (String keyValue : split) { - String[] pair = keyValue.split("="); - if (pair.length == 2) { - map.put(pair[0].trim(), pair[1].trim()); - } - } - } - } - - /** - * Add a map of key value placeholder pairs. - */ - public void add(Map placeholders) { - if (placeholders != null) { - map.putAll(placeholders); - } - } -} diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/ScriptTransform.java b/src/main/java/com/avaje/ebean/dbmigration/runner/ScriptTransform.java deleted file mode 100644 index 31a0419fd..000000000 --- a/src/main/java/com/avaje/ebean/dbmigration/runner/ScriptTransform.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import java.util.HashMap; -import java.util.Map; - -/** - * Transforms a SQL script given a map of key/value substitutions. - */ -class ScriptTransform { - - /** - * Transform just ${table} with the table name. - */ - public static String table(String tableName, String script) { - return script.replace("${table}", tableName); - } - - private final Map placeholders = new HashMap(); - - ScriptTransform(Map map) { - for (Map.Entry entry : map.entrySet()) { - placeholders.put(wrapKey(entry.getKey()), entry.getValue()); - } - } - - private String wrapKey(String key) { - return "${"+key+"}"; - } - - /** - * Return true if this contains no placeholders. - */ - boolean isEmpty() { - return placeholders.isEmpty(); - } - - /** - * Transform the script replacing placeholders in the form ${key} with value. - */ - String transform(String source) { - - for (Map.Entry entry : placeholders.entrySet()) { - source = source.replace(entry.getKey(), entry.getValue()); - } - return source; - } -} 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 602e9b778..3c346ce43 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -14,7 +14,6 @@ import com.avaje.ebean.config.EncryptKeyManager; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.DatabasePlatform; import com.avaje.ebean.dbmigration.DdlGenerator; -import com.avaje.ebean.dbmigration.MigrationRunner; import com.avaje.ebean.event.BeanPersistController; import com.avaje.ebean.event.readaudit.ReadAuditLogger; import com.avaje.ebean.event.readaudit.ReadAuditPrepare; @@ -65,6 +64,7 @@ import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager; import com.avaje.ebeaninternal.util.ParamTypeHelper; import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo; import com.avaje.ebeanservice.docstore.api.DocStoreIntegration; +import org.avaje.dbmigration.MigrationRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -344,10 +344,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { DbMigrationConfig migrationConfig = serverConfig.getMigrationConfig(); if (migrationConfig != null) { migrationConfig.generateOnStart(this); - } - if (migrationConfig.isRunMigration()) { - new MigrationRunner(this, migrationConfig).run(); + if (migrationConfig.isRunMigration()) { + // classLoader used to load resources + ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader(); + MigrationRunner runner = migrationConfig.createRunner(classLoader); + runner.run(serverConfig.getDataSource()); + } } } diff --git a/src/test/java/com/avaje/ebean/dbmigration/DdlParserTest.java b/src/test/java/com/avaje/ebean/dbmigration/DdlParserTest.java deleted file mode 100644 index e2115d1af..000000000 --- a/src/test/java/com/avaje/ebean/dbmigration/DdlParserTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.avaje.ebean.dbmigration; - -import org.junit.Test; - -import java.io.StringReader; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - - -public class DdlParserTest { - - DdlParser parser = new DdlParser(); - - @Test - public void parse_ignoresEmptyLines() throws Exception { - - List stmts = parser.parse(new StringReader("\n\none;\n\ntwo;\n\n")); - - assertThat(stmts).hasSize(2); - assertThat(stmts).contains("one;","two;"); - } - - @Test - public void parse_ignoresComments_whenFirst() throws Exception { - - List stmts = parser.parse(new StringReader("-- comment\ntwo;")); - - assertThat(stmts).hasSize(1); - assertThat(stmts).contains("two;"); - } - - @Test - public void parse_ignoresEmptyLines_whenFirst() throws Exception { - - List stmts = parser.parse(new StringReader("\n\n-- comment\ntwo;\n\n")); - assertThat(stmts).hasSize(1); - assertThat(stmts).contains("two;"); - } - - @Test - public void parse_inlineEmptyLines_replacedWithSpace() throws Exception { - - List stmts = parser.parse(new StringReader("\n\n-- comment\none\ntwo;\n\n")); - assertThat(stmts).hasSize(1); - assertThat(stmts).contains("one two;"); - } - - - @Test - public void parse_ignoresComments() throws Exception { - - List stmts = parser.parse(new StringReader("one;\n-- comment\ntwo;")); - - assertThat(stmts).hasSize(2); - assertThat(stmts).contains("one;","two;"); - } -} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/dbmigration/MigrationRunnerTest.java b/src/test/java/com/avaje/ebean/dbmigration/MigrationRunnerTest.java deleted file mode 100644 index 426319e44..000000000 --- a/src/test/java/com/avaje/ebean/dbmigration/MigrationRunnerTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.avaje.ebean.dbmigration; - -import com.avaje.ebean.BaseTestCase; -import com.avaje.ebean.Ebean; -import com.avaje.ebean.EbeanServer; -import com.avaje.ebean.config.DbMigrationConfig; -import org.junit.Test; - - -public class MigrationRunnerTest extends BaseTestCase { - - - @Test - public void test() { - - if (!isH2()) { - return; - } - - EbeanServer server = Ebean.getDefaultServer(); - - DbMigrationConfig config = new DbMigrationConfig(); - config.setMigrationPath("test-dbmigration"); - config.setRunMigration(true); - config.setDbUser("sa"); - config.setDbPassword(""); - - new MigrationRunner(server, config).run(); - } -} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/dbmigration/runner/ChecksumTest.java b/src/test/java/com/avaje/ebean/dbmigration/runner/ChecksumTest.java deleted file mode 100644 index b268c4444..000000000 --- a/src/test/java/com/avaje/ebean/dbmigration/runner/ChecksumTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ChecksumTest { - - @Test - public void test_calculate() throws Exception { - - int checkFoo = Checksum.calculate("foo"); - - assertThat(Checksum.calculate("foo")).isEqualTo(checkFoo); - assertThat(Checksum.calculate("Foo")).isNotEqualTo(checkFoo); - } - -} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilderTest.java b/src/test/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilderTest.java deleted file mode 100644 index 48f49b466..000000000 --- a/src/test/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilderTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import org.assertj.core.data.MapEntry; -import org.junit.Test; - -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - - -public class PlaceholderBuilderTest { - - @Test - public void empty() throws Exception { - - assertThat(PlaceholderBuilder.build(null, null)).isEmpty(); - } - - @Test - @SuppressWarnings("unchecked") - public void comma() throws Exception { - - assertThat(PlaceholderBuilder.build("a=1", null)) - .containsExactly(MapEntry.entry("a","1")); - } - - @Test - @SuppressWarnings("unchecked") - public void comma_withSpace() throws Exception { - - assertThat(PlaceholderBuilder.build(" a=1 ", null)) - .containsExactly(MapEntry.entry("a","1")); - } - - @Test - @SuppressWarnings("unchecked") - public void comma_withSpace_withSemi() throws Exception { - - assertThat(PlaceholderBuilder.build(" a=1 ; ", null)) - .containsExactly(MapEntry.entry("a","1")); - } - - @Test - @SuppressWarnings("unchecked") - public void commaPair() throws Exception { - - assertThat(PlaceholderBuilder.build("a=1;b=2", null)) - .containsExactly(MapEntry.entry("a","1"),MapEntry.entry("b","2")); - } - - - @Test - @SuppressWarnings("unchecked") - public void mapPair() throws Exception { - - Map in = new HashMap(); - in.put("a", "1"); - in.put("b", "2"); - - assertThat(PlaceholderBuilder.build(null, in)) - .containsExactly(MapEntry.entry("a","1"),MapEntry.entry("b","2")); - } - - @Test - @SuppressWarnings("unchecked") - public void mapPair_override() throws Exception { - - Map in = new HashMap(); - in.put("a", "1"); - in.put("b", "2"); - - assertThat(PlaceholderBuilder.build("a=11;b=12", in)) - .containsExactly(MapEntry.entry("a","1"),MapEntry.entry("b","2")); - } - - @Test - @SuppressWarnings("unchecked") - public void mapPair_join() throws Exception { - - Map in = new HashMap(); - in.put("c", "3"); - - assertThat(PlaceholderBuilder.build("a=1;b=2", in)) - .containsExactly(MapEntry.entry("a","1"),MapEntry.entry("b","2"), MapEntry.entry("c","3")); - } -} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/dbmigration/runner/ScriptTransformTest.java b/src/test/java/com/avaje/ebean/dbmigration/runner/ScriptTransformTest.java deleted file mode 100644 index 3aeff2413..000000000 --- a/src/test/java/com/avaje/ebean/dbmigration/runner/ScriptTransformTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.avaje.ebean.dbmigration.runner; - -import org.junit.Test; - -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.*; - -public class ScriptTransformTest { - - @Test - public void transform() throws Exception { - - Map map = new HashMap(); - map.put("one", "PLACE1"); - map.put("two", "PLACE2"); - - ScriptTransform transform = new ScriptTransform(map); - - assertEquals(transform.transform("${one}"), "PLACE1"); - assertEquals(transform.transform("${two}"), "PLACE2"); - assertEquals(transform.transform("${one}${two}"), "PLACE1PLACE2"); - assertEquals(transform.transform("${two}${one}"), "PLACE2PLACE1"); - assertEquals(transform.transform("A${one}B${two}C"), "APLACE1BPLACE2C"); - assertEquals(transform.transform(" ${one} ${two} "), " PLACE1 PLACE2 "); - - assertEquals(transform.transform("$${one}"), "$PLACE1"); - assertEquals(transform.transform("$${one}}"), "$PLACE1}"); - } - -} \ No newline at end of file diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 466673fd5..60e028cb1 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -76,15 +76,15 @@ - - - - + + + + - - - - + + + +