diff --git a/pom.xml b/pom.xml
index 607ed216b..f23a67d56 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,7 +64,7 @@
org.avaje
avaje-datasource
- 1.1.1
+ 1.1.2
@@ -92,12 +92,6 @@
[1.7.1,1.7.99)
-
- org.avaje
- avaje-classpath-scanner
- 1.1.1
-
-
org.springframework.boot
diff --git a/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java b/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java
index f960de5b5..9c2342f08 100644
--- a/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java
+++ b/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java
@@ -74,6 +74,9 @@ public class ClassLoadConfig {
}
}
+ /**
+ * Return the resources for the given name.
+ */
public Enumeration getResources(String name) throws IOException {
return context.getResources(name);
}
diff --git a/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java b/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java
index 4356058a2..6e890186a 100644
--- a/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java
+++ b/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java
@@ -6,6 +6,8 @@ import com.avaje.ebean.dbmigration.DbMigration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Map;
+
/**
* Configuration for the DB migration processing.
*/
@@ -67,6 +69,36 @@ public class DbMigrationConfig {
*/
protected String generatePendingDrop;
+ /**
+ * For running migration the DB table that holds migration execution status.
+ */
+ protected String metaTable = "db_migration";
+
+ /**
+ * Flag set to true means to run any outstanding migrations on startup.
+ */
+ protected boolean runMigration;
+
+ /**
+ * Comma and equals delimited key/value placeholders to replace in DDL scripts.
+ */
+ protected String runPlaceholders;
+
+ /**
+ * Map of key/value placeholders to replace in DDL scripts.
+ */
+ protected Map runPlaceholderMap;
+
+ /**
+ * DB user used to run the DB migration.
+ */
+ protected String dbUser;
+
+ /**
+ * DB password used to run the DB migration.
+ */
+ protected String dbPassword;
+
/**
* Return the DB platform to generate migration DDL for.
*
@@ -199,10 +231,119 @@ public class DbMigrationConfig {
this.modelPath = "";
}
+ /**
+ * Return the table name that holds the migration run details
+ * (used by DB Migration runner only).
+ */
+ public String getMetaTable() {
+ return metaTable;
+ }
+
+ /**
+ * Set the table name that holds the migration run details
+ * (used by DB Migration runner only).
+ */
+ public void setMetaTable(String metaTable) {
+ this.metaTable = metaTable;
+ }
+
+ /**
+ * Return a comma and equals delimited placeholders that are substituted in SQL scripts when running migration
+ * (used by DB Migration runner only).
+ */
+ public String getRunPlaceholders() {
+ // environment properties take precedence
+ String placeholders = readEnvironment("ddl.migration.placeholders");
+ if (placeholders != null) {
+ return placeholders;
+ }
+ return runPlaceholders;
+ }
+
+ /**
+ * Set a comma and equals delimited placeholders that are substituted in SQL scripts when running migration
+ * (used by DB Migration runner only).
+ */
+ public void setRunPlaceholders(String runPlaceholders) {
+ this.runPlaceholders = runPlaceholders;
+ }
+
+ /**
+ * Return a map of placeholder values that are substituted in SQL scripts when running migration
+ * (used by DB Migration runner only).
+ */
+ public Map getRunPlaceholderMap() {
+ return runPlaceholderMap;
+ }
+
+ /**
+ * Set a map of placeholder values that are substituted when running migration
+ * (used by DB Migration runner only).
+ */
+ public void setRunPlaceholderMap(Map runPlaceholderMap) {
+ this.runPlaceholderMap = runPlaceholderMap;
+ }
+
+ /**
+ * Return true if the DB migration should be run on startup.
+ */
+ public boolean isRunMigration() {
+ // environment properties take precedence
+ String run = readEnvironment("ddl.migration.run");
+ if (run != null) {
+ return "true".equalsIgnoreCase(run.trim());
+ }
+ return runMigration;
+ }
+
+ /**
+ * Set to true to run the DB migration on startup.
+ */
+ public void setRunMigration(boolean runMigration) {
+ this.runMigration = runMigration;
+ }
+
+ /**
+ * Return the DB user to use for running DB migrations.
+ */
+ public String getDbUser() {
+ // environment properties take precedence
+ String user = readEnvironment("ddl.migration.user");
+ if (user != null) {
+ return user;
+ }
+ return dbUser;
+ }
+
+ /**
+ * Set the DB user to use for running DB migrations.
+ */
+ public void setDbUser(String dbUser) {
+ this.dbUser = dbUser;
+ }
+
+ /**
+ * Return the DB password to use for running DB migrations.
+ */
+ public String getDbPassword() {
+ String user = readEnvironment("ddl.migration.password");
+ if (user != null) {
+ return user;
+ }
+ return dbPassword;
+ }
+
+ /**
+ * Set the DB password to use for running DB migrations.
+ */
+ public void setDbPassword(String dbPassword) {
+ this.dbPassword = dbPassword;
+ }
+
/**
* Load the settings from the PropertiesWrapper.
*/
- public void loadSettings(PropertiesWrapper properties) {
+ public void loadSettings(PropertiesWrapper properties, String serverName) {
migrationPath = properties.get("migration.migrationPath", migrationPath);
if (properties.getBoolean("migration.singleDirectory", false)) {
@@ -219,7 +360,19 @@ public class DbMigrationConfig {
generate = properties.getBoolean("migration.generate", generate);
version = properties.get("migration.version", version);
- name = properties.get("migration.name", name);
+ this.name = properties.get("migration.name", this.name);
+
+ runMigration = properties.getBoolean("migration.run", runMigration);
+ metaTable = properties.get("migration.metaTable", metaTable);
+ runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
+
+ String adminUser = properties.get("datasource."+serverName+".user", dbUser);
+ adminUser = properties.get("datasource."+serverName+".adminuser", adminUser);
+ dbUser = properties.get("migration.dbuser", adminUser);
+
+ String adminPwd = properties.get("datasource."+serverName+".password", dbPassword);
+ adminPwd = properties.get("datasource."+serverName+".adminpassword", adminPwd);
+ dbPassword = properties.get("migration.dbpassword", adminPwd);
}
/**
diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java
index e51f81cfd..9322e29a2 100644
--- a/src/main/java/com/avaje/ebean/config/ServerConfig.java
+++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java
@@ -2267,7 +2267,7 @@ public class ServerConfig {
*/
protected void loadSettings(PropertiesWrapper p) {
- migrationConfig.loadSettings(p);
+ migrationConfig.loadSettings(p, name);
namingConvention = createNamingConvention(p, namingConvention);
if (namingConvention != null) {
diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java
index ef378fb55..922536d63 100644
--- a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java
+++ b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java
@@ -540,6 +540,9 @@ public class DatabasePlatform {
}
}
+ /**
+ * Close the resultSet.
+ */
protected void close(ResultSet resultSet) {
try {
resultSet.close();
diff --git a/src/main/java/com/avaje/ebean/dbmigration/MigrationRunner.java b/src/main/java/com/avaje/ebean/dbmigration/MigrationRunner.java
index c00b4c77f..dac27e033 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/MigrationRunner.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/MigrationRunner.java
@@ -1,7 +1,6 @@
package com.avaje.ebean.dbmigration;
import com.avaje.ebean.EbeanServer;
-import com.avaje.ebean.Transaction;
import com.avaje.ebean.config.DbMigrationConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.runner.LocalMigrationResource;
@@ -11,62 +10,91 @@ 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 {
private static final Logger logger = LoggerFactory.getLogger(MigrationRunner.class);
- final EbeanServer server;
- final ServerConfig config;
+ private final EbeanServer server;
+
+ private final ServerConfig config;
private final DbMigrationConfig migrationConfig;
- public MigrationRunner(EbeanServer server) {
+ public MigrationRunner(EbeanServer server, DbMigrationConfig migrationConfig) {
this.server = server;
this.config = server.getPluginApi().getServerConfig();
- migrationConfig = this.config.getMigrationConfig();
+ this.migrationConfig = migrationConfig;
}
+ /**
+ * Run the migrations if there are any that need running.
+ */
public void run() {
- LocalMigrationResources resources = new LocalMigrationResources(config);
+ LocalMigrationResources resources = new LocalMigrationResources(config, migrationConfig);
if (!resources.readResources()) {
logger.debug("no migrations to check");
return;
}
- Transaction transaction = server.createTransaction();
- Connection connection = transaction.getConnection();
+ String migrationUser = migrationConfig.getDbUser();
+ String migrationPwd = migrationConfig.getDbPassword();
+
+ DataSource dataSource = server.getPluginApi().getDataSource();
+
+ Connection connection;
try {
- MigrationTable table = new MigrationTable(server, migrationConfig, connection);
- table.createIfNeeded();
+ connection = dataSource.getConnection(migrationUser, migrationPwd);
+ } catch (SQLException e) {
+ throw new IllegalArgumentException("Error trying to connect to database using DB Migration user [" + migrationUser + "]", e);
+ }
- LocalMigrationResource priorVersion = null;
- List localVersions = resources.getVersions();
- for (int i = 0; i < localVersions.size(); i++) {
- LocalMigrationResource localVersion = localVersions.get(i);
- if (i > 0) {
- priorVersion = localVersions.get(i-1);
- }
- if (!table.shouldRun(localVersion, priorVersion)) {
- break;
- }
- }
+ try {
+ connection.setAutoCommit(false);
- table.commit();
+ 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();
+
+ 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;
+ }
}
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java b/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java
index 979552e07..de67d6744 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java
@@ -4,7 +4,7 @@ import com.avaje.ebean.dbmigration.model.MigrationVersion;
import org.avaje.classpath.scanner.Resource;
/**
- * A DB migration resource.
+ * A DB migration resource (DDL script with version).
*/
public class LocalMigrationResource implements Comparable {
@@ -14,6 +14,9 @@ public class LocalMigrationResource implements Comparable versions = new ArrayList();
- public LocalMigrationResources(ServerConfig serverConfig) {
+ /**
+ * Construct with configuration options.
+ */
+ public LocalMigrationResources(ServerConfig serverConfig, DbMigrationConfig migrationConfig) {
this.serverConfig = serverConfig;
- this.migrationConfig = serverConfig.getMigrationConfig();
+ this.migrationConfig = migrationConfig;
}
+ /**
+ * Read all the migration resources (SQL scripts) returning true if there are versions.
+ */
public boolean readResources() {
String migrationPath = migrationConfig.getMigrationPath();
@@ -59,14 +64,20 @@ public class LocalMigrationResources {
return !versions.isEmpty();
}
+ /**
+ * Return the list of migration resources in version order.
+ */
public List getVersions() {
return versions;
}
- static class Match implements MatchResource {
+ /**
+ * Filter used to find the migration scripts.
+ */
+ private static class Match implements ResourceFilter {
- final DbMigrationConfig migrationConfig;
+ private final DbMigrationConfig migrationConfig;
Match(DbMigrationConfig migrationConfig) {
this.migrationConfig = migrationConfig;
@@ -74,12 +85,7 @@ public class LocalMigrationResources {
@Override
public boolean isMatch(String name) {
-
- return name.endsWith(migrationConfig.getApplySuffix())
- || name.endsWith(migrationConfig.getModelSuffix())
- || name.endsWith(migrationConfig.getDropSuffix())
- || name.endsWith(migrationConfig.getRollbackSuffix());
-
+ 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
index 5a066ac87..ae3beff8b 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationMetaRow.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationMetaRow.java
@@ -1,27 +1,100 @@
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.
*/
-public class MigrationMetaRow implements Comparable {
+class MigrationMetaRow {
- int id;
- String status;
- String runVersion;
- String depVersion;
- String comment;
- int checksum;
- Timestamp runOn;
- String runBy;
+ private int id;
- @Override
- public int compareTo(MigrationMetaRow o) {
- return (id < o.id) ? -1 : ((id == o.id) ? 0 : 1);
+ private String status;
+
+ private String runVersion;
+
+ private String depVersion;
+
+ private String comment;
+
+ private int checksum;
+
+ private Timestamp runOn;
+
+ private String runBy;
+
+ /**
+ * Construct for inserting into table.
+ */
+ MigrationMetaRow(int id, String runVersion, String priorVersion, String comment, int checksum, String runBy) {
+ this.id = id;
+ this.runVersion = runVersion;
+ this.depVersion = priorVersion;
+ this.checksum = checksum;
+ this.comment = comment;
+ this.runBy = runBy;
+ this.runOn = new Timestamp(System.currentTimeMillis());
}
+ /**
+ * Construct from the SqlRow (read from table).
+ */
+ MigrationMetaRow(SqlRow row) {
+ id = row.getInteger("id");
+ status = row.getString("status");
+ runVersion = row.getString("row_version");
+ depVersion = row.getString("dep_version");
+ comment = row.getString("comment");
+ checksum = row.getInteger("checksum");
+ runOn = row.getTimestamp("run_on");
+ runBy = row.getString("run_by");
+ }
- //String sql = "insert into ebean_migration (id,status,run_version,dep_version,comment,checksum,run_on,run_by,run_ip) "+
- // "values (?,?,?,?,?,?,?,?,?)";
+ /**
+ * Return the id for this migration.
+ */
+ int getId() {
+ return id;
+ }
+
+ /**
+ * Return the normalised version for this migration.
+ */
+ String getRunVersion() {
+ return runVersion;
+ }
+
+ /**
+ * 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, "success");
+ insert.setParameter(3, runVersion);
+ insert.setParameter(4, depVersion);
+ insert.setParameter(5, comment);
+ insert.setParameter(6, checksum);
+ insert.setParameter(7, runOn);
+ insert.setParameter(8, runBy);
+ insert.setParameter(9, "ip");
+ }
+
+ /**
+ * Return the SQL insert given the table migration meta data is stored in.
+ */
+ static String insertSql(String table) {
+ return "insert into " + table
+ + " (id, status, run_version, dep_version, comment, checksum, run_on, run_by, run_ip)"
+ + " 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
index f52ae6caf..4ef20cd7c 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationScriptRunner.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationScriptRunner.java
@@ -5,12 +5,15 @@ import com.avaje.ebean.dbmigration.DdlRunner;
import java.sql.Connection;
/**
- * Created by rob on 5/02/16.
+ * Runs the DDL migration scripts.
*/
public class MigrationScriptRunner {
- final Connection connection;
+ private final Connection connection;
+ /**
+ * Construct with a given connection.
+ */
public MigrationScriptRunner(Connection connection) {
this.connection = 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
index c5d15a6ad..c97e8cede 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationTable.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationTable.java
@@ -17,12 +17,13 @@ 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;
/**
- * Created by rob on 5/02/16.
+ * Manages the migration table.
*/
public class MigrationTable {
@@ -34,34 +35,53 @@ public class MigrationTable {
private final DatabasePlatform databasePlatform;
- private final DbMigrationConfig migrationConfig;
-
private final String catalog;
private final String schema;
private final String table;
private final ServerConfig serverConfig;
private final String envUserName;
- int currentSearchPosition;
+ private final ScriptTransform scriptTransform;
- List metaRows;
+ 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.migrationConfig = migrationConfig;
+
this.catalog = null;
this.schema = null;
- this.table = "ebean_migration";
+ this.table = migrationConfig.getMetaTable();
+ this.insertSql = MigrationMetaRow.insertSql(table);
+ this.scriptTransform = createScriptTransform(migrationConfig);
this.envUserName = System.getProperty("user.name");
}
+ /**
+ * 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)) {
@@ -69,20 +89,39 @@ public class MigrationTable {
}
ExternalJdbcTransaction t = new ExternalJdbcTransaction(connection);
- SqlQuery sqlQuery = server.createSqlQuery("select * from ebean_migration order by id for update");
- metaRows = server.findList(sqlQuery, t);
+ SqlQuery sqlQuery = server.createSqlQuery("select * from "+table+" order by id for update");
+ List metaRows = server.findList(sqlQuery, t);
+
+ for (SqlRow row : metaRows) {
+ addMigration(new MigrationMetaRow(row));
+ }
}
+
private void createTable(Connection connection) throws IOException {
- String script = getCreateTableScript();
+ String script = ScriptTransform.table(table, getCreateTableScript());
+
MigrationScriptRunner run = new MigrationScriptRunner(connection);
- run.runScript(false, script, "create migration tables");
+ 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;
+ }
- Enumeration resources = serverConfig.getClassLoadConfig().getResources("migration-support/create.sql");
+ 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());
@@ -90,75 +129,106 @@ public class MigrationTable {
return null;
}
- public boolean tableExists(Connection connection) throws SQLException {
-
+ /**
+ * Return true if the table exists.
+ */
+ private boolean tableExists(Connection connection) throws SQLException {
return databasePlatform.tableExists(connection, catalog, schema, table);
}
-
+ /**
+ * Return true if the migration ran successfully and false if the migration failed.
+ */
public boolean shouldRun(LocalMigrationResource localVersion, LocalMigrationResource priorVersion) {
- // if prior != null check previous version installed
- // if previous version not installed ... error - missing version (prior version)
-
-// if (!priorVersionNotInstalled(priorVersion)) {
-//
-// }
-//
-// // if localVersion installed
-// // check checksum and return ok, or re-installable?
-// // else
-// // install version and continue
-//
-// if (runPosition >= metaRows.size()) {
-// logger.debug("No matching row");
-// runMigration(runPosition, localVersion);
-// return true;
-// }
-
- return false;
- }
-
- private void checkInstalled(LocalMigrationResource priorVersion) {
if (priorVersion != null) {
- searchFor(priorVersion);
+ // check priorVersion is installed
+ MigrationMetaRow existing = migrations.get(priorVersion.getVersion().normalised());
+ if (existing == null) {
+ logger.warn("Migration {} requires prior migration {} which has not been run", localVersion.getVersion(), priorVersion.getVersion());
+ return false;
+ }
+ }
+
+ MigrationMetaRow existing = migrations.get(localVersion.getVersion().normalised());
+ if (existing == null) {
+ runMigration(localVersion, priorVersion);
+ return true;
+
+ } else {
+ // check checksum and return ok, or re-run if repeatable script?
+ existing.getChecksum();
+ return true;
}
}
- private void searchFor(LocalMigrationResource priorVersion) {
-
- }
-
- public void commit() throws SQLException {
- connection.commit();
- }
-
- private void runMigration(int runPosition, LocalMigrationResource localVersion) {
+ /**
+ * Run the migration script.
+ */
+ private void runMigration(LocalMigrationResource localVersion, LocalMigrationResource prior) {
logger.debug("run migration "+localVersion.getLocation());
-
- String script = localVersion.getContent();
+ String script = convertScript(localVersion.getContent());
MigrationScriptRunner run = new MigrationScriptRunner(connection);
run.runScript(false, script, "run migration version: "+localVersion.getVersion());
- String sql = "insert into ebean_migration (id,status,run_version,dep_version,comment,checksum,run_on,run_by,run_ip) "+
- "values (?,?,?,?,?,?,?,?,?)";
- SqlUpdate sqlUpdate = server.createSqlUpdate(sql);
- sqlUpdate.setParameter(1, runPosition+1);
- sqlUpdate.setParameter(2, "success");
- sqlUpdate.setParameter(3, localVersion.getVersion().getFull());
- sqlUpdate.setParameter(4, "na");
- sqlUpdate.setParameter(5, "comm");
- sqlUpdate.setParameter(6, 0);
- sqlUpdate.setParameter(7, new Timestamp(System.currentTimeMillis()));
- sqlUpdate.setParameter(8, envUserName);
- sqlUpdate.setParameter(9, "userIp");
+ int checksum = Checksum.calculate(script);
+ MigrationMetaRow metaRow = createMetaRow(localVersion, prior, checksum);
- ExternalJdbcTransaction t = new ExternalJdbcTransaction(connection);
- server.execute(sqlUpdate, t);
+ SqlUpdate insert = server.createSqlUpdate(insertSql);
+ metaRow.bindInsert(insert);
+ server.execute(insert, new ExternalJdbcTransaction(connection));
+ addMigration(metaRow);
+ }
+
+ /**
+ * Create the MigrationMetaRow for this migration.
+ */
+ private MigrationMetaRow createMetaRow(LocalMigrationResource localVersion, LocalMigrationResource prior, int checksum) {
+
+ int nextId = 1;
+ if (lastMigration != null) {
+ nextId = lastMigration.getId() + 1;
+ }
+
+ String runVersion = localVersion.getVersion().normalised();
+ String comment = getMigrationComment(localVersion);
+ String priorVersion = getMigrationPriorVersion(prior);
+
+ return new MigrationMetaRow(nextId, runVersion, priorVersion, comment, checksum, envUserName);
+ }
+
+ /**
+ * Return the prior migration normalised version.
+ */
+ private String getMigrationPriorVersion(LocalMigrationResource prior) {
+ return prior != null ? prior.getVersion().normalised() : "-";
+ }
+
+ /**
+ * Return the migration comment.
+ */
+ private String getMigrationComment(LocalMigrationResource localVersion) {
+ String comment = localVersion.getVersion().getComment();
+ return comment == null || comment.isEmpty() ? "-" : comment;
+ }
+
+ /**
+ * 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(MigrationMetaRow metaRow) {
+ lastMigration = metaRow;
+ migrations.put(metaRow.getRunVersion(), 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
index 0789b474d..eec825394 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilder.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilder.java
@@ -1,7 +1,53 @@
package com.avaje.ebean.dbmigration.runner;
+import java.util.HashMap;
+import java.util.Map;
+
/**
- * Created by rob on 1/04/16.
+ * Joins placeholder map and comma/equals delimited string.
*/
-public class PlaceholderBuilder {
+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
index d208527d0..31a0419fd 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/runner/ScriptTransform.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/ScriptTransform.java
@@ -1,7 +1,47 @@
package com.avaje.ebean.dbmigration.runner;
+import java.util.HashMap;
+import java.util.Map;
+
/**
- * Created by rob on 1/04/16.
+ * Transforms a SQL script given a map of key/value substitutions.
*/
-public class ScriptTransform {
+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/ebean/plugin/SpiServer.java b/src/main/java/com/avaje/ebean/plugin/SpiServer.java
index 12a196339..73dcacecb 100644
--- a/src/main/java/com/avaje/ebean/plugin/SpiServer.java
+++ b/src/main/java/com/avaje/ebean/plugin/SpiServer.java
@@ -4,6 +4,7 @@ import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
+import javax.sql.DataSource;
import java.util.List;
/**
@@ -40,4 +41,9 @@ public interface SpiServer extends EbeanServer {
* Return the bean type for a given doc store queueId.
*/
BeanType> getBeanTypeForQueueId(String queueId);
+
+ /**
+ * Return the associated DataSource for this EbeanServer instance.
+ */
+ DataSource getDataSource();
}
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 657bbd20c..c9ef5f557 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -20,8 +20,8 @@ import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.plugin.BeanType;
-import com.avaje.ebean.plugin.SpiServer;
import com.avaje.ebean.plugin.Plugin;
+import com.avaje.ebean.plugin.SpiServer;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebeaninternal.api.LoadBeanRequest;
@@ -73,6 +73,7 @@ import org.slf4j.LoggerFactory;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
+import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -80,7 +81,6 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.FutureTask;
@@ -308,6 +308,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return autoTuneService;
}
+ @Override
+ public DataSource getDataSource() {
+ return transactionManager.getDataSource();
+ }
+
@Override
public ReadAuditPrepare getReadAuditPrepare() {
return readAuditPrepare;
@@ -341,8 +346,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
migrationConfig.generateOnStart(this);
}
- MigrationRunner migrationRunner = new MigrationRunner(this);
- migrationRunner.run();
+ if (migrationConfig.isRunMigration()) {
+ new MigrationRunner(this, migrationConfig).run();
+ }
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/util/EncodeUtil.java b/src/main/java/com/avaje/ebeaninternal/util/EncodeUtil.java
index e5c45aff9..946066c7e 100644
--- a/src/main/java/com/avaje/ebeaninternal/util/EncodeUtil.java
+++ b/src/main/java/com/avaje/ebeaninternal/util/EncodeUtil.java
@@ -15,7 +15,6 @@ public final class EncodeUtil {
/* no instances */
}
-
/**
* URL-encodes the specified UTF-8 string.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/util/IOUtils.java b/src/main/java/com/avaje/ebeaninternal/util/IOUtils.java
index 4d89e28b3..41a74b8e8 100644
--- a/src/main/java/com/avaje/ebeaninternal/util/IOUtils.java
+++ b/src/main/java/com/avaje/ebeaninternal/util/IOUtils.java
@@ -7,9 +7,10 @@ import java.io.OutputStream;
import java.io.Reader;
/**
- * Created by rob on 5/02/16.
+ * Utilities for IO.
*/
public class IOUtils {
+
/**
* Reads the entire contents of the specified input stream and returns them
* as a byte array.
@@ -44,6 +45,9 @@ public class IOUtils {
return EncodeUtil.decodeBytes(read(in), encoding);
}
+ /**
+ * Read the entire contents from the reader returning as a String.
+ */
public static String read(Reader reader) throws IOException {
StringBuilder sb = new StringBuilder();
diff --git a/src/main/java/com/avaje/ebeaninternal/util/JdbcClose.java b/src/main/java/com/avaje/ebeaninternal/util/JdbcClose.java
index bfdb83532..e9a9cbd34 100644
--- a/src/main/java/com/avaje/ebeaninternal/util/JdbcClose.java
+++ b/src/main/java/com/avaje/ebeaninternal/util/JdbcClose.java
@@ -23,4 +23,15 @@ public class JdbcClose {
logger.warn("Error closing connection", e);
}
}
+
+ /**
+ * Rollback the connection logging if an error occurs.
+ */
+ public static void rollback(Connection connection) {
+ try {
+ connection.rollback();
+ } catch (SQLException e) {
+ logger.warn("Error on connection rollback", e);
+ }
+ }
}
diff --git a/src/main/resources/migration-support/default-create-table.sql b/src/main/resources/migration-support/default-create-table.sql
index e69de29bb..747975589 100644
--- a/src/main/resources/migration-support/default-create-table.sql
+++ b/src/main/resources/migration-support/default-create-table.sql
@@ -0,0 +1,13 @@
+create table ${table} (
+ id integer not null,
+ status varchar(10) not null,
+ run_version varchar(150) not null,
+ dep_version varchar(150) not null,
+ comment varchar(150),
+ checksum integer not null,
+ run_on timestamp not null,
+ run_by varchar(30) not null,
+ run_ip varchar(30),
+ constraint pk_${table} primary key (id)
+);
+
diff --git a/src/test/java/com/avaje/ebean/dbmigration/MigrationRunnerTest.java b/src/test/java/com/avaje/ebean/dbmigration/MigrationRunnerTest.java
index aafc2da51..c3189cea0 100644
--- a/src/test/java/com/avaje/ebean/dbmigration/MigrationRunnerTest.java
+++ b/src/test/java/com/avaje/ebean/dbmigration/MigrationRunnerTest.java
@@ -1,10 +1,27 @@
package com.avaje.ebean.dbmigration;
-import static org.junit.Assert.*;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.EbeanServer;
+import com.avaje.ebean.config.DbMigrationConfig;
+import org.junit.Test;
+
-/**
- * Created by rob on 1/04/16.
- */
public class MigrationRunnerTest {
+
+ @Test
+ public void test() {
+
+ EbeanServer server = Ebean.getDefaultServer();
+
+ DbMigrationConfig config = new DbMigrationConfig();
+ config.setMigrationPath("test-dbmigration");
+ config.setRunMigration(true);
+ config.setDbUser("sa");
+ config.setDbPassword("");
+
+ MigrationRunner runner = new MigrationRunner(server, config);
+
+ runner.run();
+ }
}
\ 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
index b12c63002..48f49b466 100644
--- a/src/test/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilderTest.java
+++ b/src/test/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilderTest.java
@@ -1,16 +1,87 @@
package com.avaje.ebean.dbmigration.runner;
+import org.assertj.core.data.MapEntry;
import org.junit.Test;
-import static org.junit.Assert.*;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
-/**
- * Created by rob on 1/04/16.
- */
public class PlaceholderBuilderTest {
- @Test
- public void build() throws Exception {
+ @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
index 96309c25d..3aeff2413 100644
--- a/src/test/java/com/avaje/ebean/dbmigration/runner/ScriptTransformTest.java
+++ b/src/test/java/com/avaje/ebean/dbmigration/runner/ScriptTransformTest.java
@@ -2,15 +2,31 @@ package com.avaje.ebean.dbmigration.runner;
import org.junit.Test;
+import java.util.HashMap;
+import java.util.Map;
+
import static org.junit.Assert.*;
-/**
- * Created by rob on 1/04/16.
- */
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