Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce MigrationContext to be prepared for JDBC migration refactor #152

Merged
merged 2 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great, if ebean-migration can have a dependency to have access to "Database". What do you think about this idea?

Otherwise, I plan to imlement a EbeanMigrationContext extends DefaultMigrationContext, that has a Database getDb() method in our application and cast to this context in our JdbcMigrations

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have access to "Database".

Yes, I would expect it along with a ebean Transaction - so with Database and Transaction then Jdbc Migrations can use all the features of ebean etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I don't expect this DefaultMigrationContext to be public.

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));
rbygrave marked this conversation as resolved.
Show resolved Hide resolved
} 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
Loading