Skip to content

Commit

Permalink
Merge pull request #152 from FOCONIS/migration-context
Browse files Browse the repository at this point in the history
Introduce MigrationContext to be prepared for JDBC migration refactor
  • Loading branch information
rbygrave authored Dec 21, 2023
2 parents bce84e0 + 769b0c1 commit 3203ba4
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.ebean.migration;

import java.sql.Connection;

/**
* The current context while a migration runs.
* <p>
* This is used to provide meta-informations in JDBC migrations and mainly provides a read-only access
* to a subset of MigrationConfig.
* <p>
* It is possible to provide an extended implementation in <code>MigrationEngine.run(context)</code>,
* which is accessible in JdbcMigration. So you can create a EbeanMigrationContext, so that you can
* access the current ebean server in the JDBC migration.
*
* @author Roland Praml, FOCONIS AG
*/
public interface MigrationContext {
/**
* The current connection. Note: During migration, this connection is always the same.
* You must not close this connection!
*/
Connection connection();

/**
* The migration path of SQL migrations. You can use this, to load additional SQL resources
* in your JDBC migration or determine, if this JDBC migration is for a particular path.
* This can be used if you have multiple ebean servers for different databases.
*/
String migrationPath();

/**
* The platform of the current migration run. (e.g. <code>sqlserver17</code>)
*/
String platform();

/**
* The base platform of the current migration run. (e.g. <code>sqlserver</code>)
*/
String basePlatform();

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ public List<MigrationResource> checkState(Connection connection) {
return run(connection, true);
}

/**
* Return the migrations that would be applied if the migration is run.
*/
public List<MigrationResource> checkState(MigrationContext context) {
return run(context, true);
}

/**
* Run by creating a DB connection from driver, url, username defined in MigrationConfig.
*/
Expand All @@ -65,6 +72,13 @@ public void run(Connection connection) {
run(connection, false);
}

/**
* Run the migrations if there are any that need running.
*/
public void run(MigrationContext context) {
run(context, false);
}

private Connection connection(DataSource dataSource) {
String username = migrationConfig.getDbUsername();
try {
Expand All @@ -86,4 +100,10 @@ private List<MigrationResource> run(Connection connection, boolean checkStateOnl
return new MigrationEngine(migrationConfig, checkStateOnly).run(connection);
}

/**
* Run the migrations if there are any that need running.
*/
private List<MigrationResource> run(MigrationContext context, boolean checkStateOnly) {
return new MigrationEngine(migrationConfig, checkStateOnly).run(context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.ebean.migration.runner;

import io.ebean.migration.MigrationConfig;
import io.ebean.migration.MigrationContext;

import java.sql.Connection;

/**
* A default implementation of the MigrationContext.
*
* @author Roland Praml, FOCONIS AG
*/
public class DefaultMigrationContext implements MigrationContext {
private final Connection connection;
private final String migrationPath;
private final String platform;
private final String basePlatform;

public DefaultMigrationContext(MigrationConfig config, Connection connection) {
this.connection = connection;
this.migrationPath = config.getMigrationPath();
this.platform = config.getPlatform();
this.basePlatform = config.getBasePlatform();
}

@Override
public Connection connection() {
return connection;
}

@Override
public String migrationPath() {
return migrationPath;
}

@Override
public String platform() {
return platform;
}

@Override
public String basePlatform() {
return basePlatform;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.ebean.migration.runner;

import io.ebean.migration.MigrationConfig;
import io.ebean.migration.MigrationContext;

import java.sql.Connection;
import java.sql.SQLException;
Expand All @@ -15,17 +16,17 @@ final class FirstCheck {

final MigrationConfig config;
final MigrationPlatform platform;
final Connection connection;
final MigrationContext context;
final String schema;
final String table;
final String sqlTable;
boolean tableKnownToExist;
private int count;

FirstCheck(MigrationConfig config, Connection connection, MigrationPlatform platform) {
FirstCheck(MigrationConfig config, MigrationContext context, MigrationPlatform platform) {
this.config = config;
this.platform = platform;
this.connection = connection;
this.context = context;
this.schema = config.getDbSchema();
this.table = config.getMetaTable();
this.sqlTable = schema != null ? schema + '.' + table : table;
Expand Down Expand Up @@ -80,7 +81,7 @@ private int checksumFor(LocalMigrationResource local) {
}

List<MigrationMetaRow> fastRead() throws SQLException {
return platform.fastReadMigrations(sqlTable, connection);
return platform.fastReadMigrations(sqlTable, context.connection());
}

int count() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.avaje.applog.AppLog;
import io.ebean.migration.MigrationConfig;
import io.ebean.migration.MigrationContext;
import io.ebean.migration.MigrationException;
import io.ebean.migration.MigrationResource;

Expand Down Expand Up @@ -35,51 +36,63 @@ public MigrationEngine(MigrationConfig migrationConfig, boolean checkStateOnly)

/**
* Run the migrations if there are any that need running.
*
* @param connection the connection to run on. Note the connection will be closed.
*/
public List<MigrationResource> run(Connection connection) {
try {
long startMs = System.currentTimeMillis();
LocalMigrationResources resources = new LocalMigrationResources(migrationConfig);
if (!resources.readResources() && !resources.readInitResources()) {
log.log(DEBUG, "no migrations to check");
return emptyList();
}
long splitMs = System.currentTimeMillis() - startMs;
final var platform = derivePlatform(migrationConfig, connection);
final var firstCheck = new FirstCheck(migrationConfig, connection, platform);
if (fastMode && firstCheck.fastModeCheck(resources.versions())) {
long checkMs = System.currentTimeMillis() - startMs;
log.log(INFO, "DB migrations completed in {0}ms - totalMigrations:{1} readResources:{2}ms", checkMs, firstCheck.count(), splitMs);
return emptyList();
}
// ensure running with autoCommit false
setAutoCommitFalse(connection);

final MigrationTable table = initialiseMigrationTable(firstCheck, connection);
try {
List<MigrationResource> result = runMigrations(table, resources.versions());
connection.commit();
if (!checkStateOnly) {
long commitMs = System.currentTimeMillis();
log.log(INFO, "DB migrations completed in {0}ms - executed:{1} totalMigrations:{2} mode:{3}", (commitMs - startMs), table.count(), table.size(), table.mode());
int countNonTransactional = table.runNonTransactional();
if (countNonTransactional > 0) {
log.log(INFO, "Non-transactional DB migrations completed in {0}ms - executed:{1}", (System.currentTimeMillis() - commitMs), countNonTransactional);
}
return run(new DefaultMigrationContext(migrationConfig, connection));
} finally {
close(connection);
}
}

/**
* Run the migrations if there are any that need running. (Does not close connection)
*/
public List<MigrationResource> run(MigrationContext context) {

long startMs = System.currentTimeMillis();
LocalMigrationResources resources = new LocalMigrationResources(migrationConfig);
if (!resources.readResources() && !resources.readInitResources()) {
log.log(DEBUG, "no migrations to check");
return emptyList();
}

var connection = context.connection();
long splitMs = System.currentTimeMillis() - startMs;
final var platform = derivePlatform(migrationConfig, connection);
final var firstCheck = new FirstCheck(migrationConfig, context, platform);
if (fastMode && firstCheck.fastModeCheck(resources.versions())) {
long checkMs = System.currentTimeMillis() - startMs;
log.log(INFO, "DB migrations completed in {0}ms - totalMigrations:{1} readResources:{2}ms", checkMs, firstCheck.count(), splitMs);
return emptyList();
}
// ensure running with autoCommit false
setAutoCommitFalse(connection);

final MigrationTable table = initialiseMigrationTable(firstCheck, connection);
try {
List<MigrationResource> result = runMigrations(table, resources.versions());
connection.commit();
if (!checkStateOnly) {
long commitMs = System.currentTimeMillis();
log.log(INFO, "DB migrations completed in {0}ms - executed:{1} totalMigrations:{2} mode:{3}", (commitMs - startMs), table.count(), table.size(), table.mode());
int countNonTransactional = table.runNonTransactional();
if (countNonTransactional > 0) {
log.log(INFO, "Non-transactional DB migrations completed in {0}ms - executed:{1}", (System.currentTimeMillis() - commitMs), countNonTransactional);
}
return result;
} catch (MigrationException e) {
rollback(connection);
throw e;
} catch (Throwable e) {
log.log(ERROR, "Perform rollback due to DB migration error", e);
rollback(connection);
throw new MigrationException("Error running DB migrations", e);
} finally {
table.unlockMigrationTable();
}
return result;
} catch (MigrationException e) {
rollback(connection);
throw e;
} catch (Throwable e) {
log.log(ERROR, "Perform rollback due to DB migration error", e);
rollback(connection);
throw new MigrationException("Error running DB migrations", e);
} finally {
close(connection);
table.unlockMigrationTable();
}
}

Expand Down
Loading

0 comments on commit 3203ba4

Please sign in to comment.