killbill-uncached
Changes
util/pom.xml 6(+6 -0)
Details
util/pom.xml 6(+6 -0)
diff --git a/util/pom.xml b/util/pom.xml
index 67543c8..0b7370e 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -111,6 +111,12 @@
<artifactId>shiro-guice</artifactId>
</dependency>
<dependency>
+ <groupId>org.flywaydb</groupId>
+ <artifactId>flyway-core</artifactId>
+ <version>4.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
</dependency>
diff --git a/util/src/test/java/org/flywaydb/core/FlywayWithDryRun.java b/util/src/test/java/org/flywaydb/core/FlywayWithDryRun.java
new file mode 100644
index 0000000..a8ae016
--- /dev/null
+++ b/util/src/test/java/org/flywaydb/core/FlywayWithDryRun.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.flywaydb.core;
+
+import java.sql.Connection;
+import java.util.List;
+
+import org.flywaydb.core.api.FlywayException;
+import org.flywaydb.core.api.callback.FlywayCallback;
+import org.flywaydb.core.api.resolver.MigrationResolver;
+import org.flywaydb.core.internal.dbsupport.DbSupport;
+import org.flywaydb.core.internal.dbsupport.Schema;
+import org.flywaydb.core.internal.dbsupport.SqlStatement;
+import org.flywaydb.core.internal.dbsupport.Table;
+import org.flywaydb.core.internal.metadatatable.MetaDataTable;
+import org.flywaydb.core.internal.util.PlaceholderReplacer;
+import org.killbill.billing.util.migration.DbMigrateWithDryRun;
+
+public class FlywayWithDryRun extends Flyway {
+
+ private final List<SqlStatement> sqlStatements;
+
+ public FlywayWithDryRun(final List<SqlStatement> sqlStatements) {
+ this.sqlStatements = sqlStatements;
+ }
+
+ // Note: we assume the schemas have already been created and baseline() has already been called
+ public int dryRunMigrate() throws FlywayException {
+ final PlaceholderReplacer placeholderReplacer = new PlaceholderReplacer(getPlaceholders(),
+ getPlaceholderPrefix(),
+ getPlaceholderSuffix());
+ return execute(new Command<Integer>() {
+ public Integer execute(final Connection connectionMetaDataTable,
+ final Connection connectionUserObjects,
+ final MigrationResolver migrationResolver,
+ final MetaDataTable metaDataTable,
+ final DbSupport dbSupport,
+ final Schema[] schemas,
+ final FlywayCallback[] flywayCallbacks) {
+ final Table metaDataDBTable = schemas[0].getTable(getTable());
+
+ @SuppressWarnings("deprecation")
+ final DbMigrateWithDryRun dbMigrate = new DbMigrateWithDryRun(sqlStatements,
+ placeholderReplacer,
+ getEncoding(),
+ metaDataDBTable,
+ connectionMetaDataTable,
+ connectionUserObjects,
+ dbSupport,
+ metaDataTable,
+ schemas[0],
+ migrationResolver,
+ getTarget(),
+ isIgnoreFutureMigrations(),
+ isIgnoreFailedFutureMigration(),
+ isOutOfOrder(),
+ flywayCallbacks);
+ return dbMigrate.dryRunMigrate();
+ }
+ });
+ }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/migration/CapturingMetaDataTable.java b/util/src/test/java/org/killbill/billing/util/migration/CapturingMetaDataTable.java
new file mode 100644
index 0000000..164a58a
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/migration/CapturingMetaDataTable.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.migration;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.flywaydb.core.api.MigrationVersion;
+import org.flywaydb.core.internal.dbsupport.DbSupport;
+import org.flywaydb.core.internal.dbsupport.SqlStatement;
+import org.flywaydb.core.internal.dbsupport.Table;
+import org.flywaydb.core.internal.metadatatable.AppliedMigration;
+import org.flywaydb.core.internal.metadatatable.MetaDataTableImpl;
+
+public class CapturingMetaDataTable extends MetaDataTableImpl {
+
+ private final List<SqlStatement> sqlStatements;
+ private final DbSupport dbSupport;
+ private final Table table;
+
+ /**
+ * Creates a new instance of the metadata table support.
+ *
+ * @param sqlStatements The current list of all pending migrations.
+ * @param dbSupport Database-specific functionality.
+ * @param table The metadata table used by flyway.
+ */
+ public CapturingMetaDataTable(final List<SqlStatement> sqlStatements, final DbSupport dbSupport, final Table table) {
+ super(dbSupport, table);
+ this.sqlStatements = sqlStatements;
+ this.dbSupport = dbSupport;
+ this.table = table;
+ }
+
+ @Override
+ public void addAppliedMigration(final AppliedMigration appliedMigration) {
+ final MigrationVersion version = appliedMigration.getVersion();
+ final String versionStr = version == null ? null : version.toString();
+ final int calculateInstalledRank;
+ try {
+ calculateInstalledRank = calculateInstalledRank();
+ } catch (final SQLException e) {
+ throw new RuntimeException(e);
+ }
+
+ final String sql = new StringBuilder().append("INSERT INTO ")
+ .append(table)
+ .append(" (")
+ .append(dbSupport.quote("installed_rank")).append(",")
+ .append(dbSupport.quote("version")).append(",")
+ .append(dbSupport.quote("description")).append(",")
+ .append(dbSupport.quote("type")).append(",")
+ .append(dbSupport.quote("script")).append(",")
+ .append(dbSupport.quote("checksum")).append(",")
+ .append(dbSupport.quote("installed_by")).append(",")
+ .append(dbSupport.quote("execution_time")).append(",")
+ .append(dbSupport.quote("success"))
+ .append(")")
+ .append(" VALUES (")
+ .append(calculateInstalledRank + appliedMigration.getInstalledRank()).append(",")
+ .append("'").append(versionStr).append("',")
+ .append("'").append(appliedMigration.getDescription()).append("',")
+ .append("'").append(appliedMigration.getType().name()).append("',")
+ .append("'").append(appliedMigration.getScript()).append("',")
+ .append(appliedMigration.getChecksum()).append(",")
+ .append(dbSupport.getCurrentUserFunction()).append(",")
+ .append(appliedMigration.getExecutionTime()).append(",")
+ .append(appliedMigration.isSuccess())
+ .append(")")
+ .toString();
+
+ sqlStatements.add(new SqlStatement(0, sql, false));
+ }
+
+ /**
+ * Calculates the installed rank for the new migration to be inserted.
+ *
+ * @return The installed rank.
+ */
+ private int calculateInstalledRank() throws SQLException {
+ final int currentMax = dbSupport.getJdbcTemplate().queryForInt("SELECT MAX(" + dbSupport.quote("installed_rank") + ")" + " FROM " + table);
+ return currentMax + 1;
+ }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/migration/CapturingSqlMigrationExecutor.java b/util/src/test/java/org/killbill/billing/util/migration/CapturingSqlMigrationExecutor.java
new file mode 100644
index 0000000..6360b75
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/migration/CapturingSqlMigrationExecutor.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.migration;
+
+import java.sql.Connection;
+import java.util.List;
+
+import org.flywaydb.core.api.resolver.MigrationExecutor;
+import org.flywaydb.core.internal.dbsupport.DbSupport;
+import org.flywaydb.core.internal.dbsupport.SqlScript;
+import org.flywaydb.core.internal.dbsupport.SqlStatement;
+import org.flywaydb.core.internal.util.PlaceholderReplacer;
+import org.flywaydb.core.internal.util.scanner.Resource;
+
+public class CapturingSqlMigrationExecutor implements MigrationExecutor {
+
+ private final List<SqlStatement> sqlStatements;
+ private final DbSupport dbSupport;
+ private final PlaceholderReplacer placeholderReplacer;
+ private final Resource sqlScriptResource;
+ private final String encoding;
+
+ /**
+ * Creates a new sql script migration based on this sql script.
+ *
+ * @param sqlStatements The current list of all pending migrations.
+ * @param dbSupport The database-specific support.
+ * @param sqlScriptResource The resource containing the sql script.
+ * @param placeholderReplacer The placeholder replacer to apply to sql migration scripts.
+ * @param encoding The encoding of this Sql migration.
+ */
+ public CapturingSqlMigrationExecutor(final List<SqlStatement> sqlStatements,
+ final DbSupport dbSupport,
+ final Resource sqlScriptResource,
+ final PlaceholderReplacer placeholderReplacer,
+ final String encoding) {
+ this.sqlStatements = sqlStatements;
+ this.dbSupport = dbSupport;
+ this.sqlScriptResource = sqlScriptResource;
+ this.encoding = encoding;
+ this.placeholderReplacer = placeholderReplacer;
+ }
+
+ @Override
+ public void execute(final Connection connection) {
+ final SqlScript sqlScript = new SqlScript(dbSupport, sqlScriptResource, placeholderReplacer, encoding);
+ for (final SqlStatement sqlStatement : sqlScript.getSqlStatements()) {
+ sqlStatements.add(sqlStatement);
+ }
+ }
+
+ @Override
+ public boolean executeInTransaction() {
+ return true;
+ }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/migration/DbMigrateWithDryRun.java b/util/src/test/java/org/killbill/billing/util/migration/DbMigrateWithDryRun.java
new file mode 100644
index 0000000..7b81a39
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/migration/DbMigrateWithDryRun.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.migration;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.flywaydb.core.api.FlywayException;
+import org.flywaydb.core.api.MigrationInfo;
+import org.flywaydb.core.api.MigrationVersion;
+import org.flywaydb.core.api.callback.FlywayCallback;
+import org.flywaydb.core.api.resolver.MigrationExecutor;
+import org.flywaydb.core.api.resolver.MigrationResolver;
+import org.flywaydb.core.internal.command.DbMigrate;
+import org.flywaydb.core.internal.dbsupport.DbSupport;
+import org.flywaydb.core.internal.dbsupport.DbSupportFactory;
+import org.flywaydb.core.internal.dbsupport.Schema;
+import org.flywaydb.core.internal.dbsupport.SqlStatement;
+import org.flywaydb.core.internal.dbsupport.Table;
+import org.flywaydb.core.internal.info.MigrationInfoImpl;
+import org.flywaydb.core.internal.info.MigrationInfoServiceImpl;
+import org.flywaydb.core.internal.metadatatable.AppliedMigration;
+import org.flywaydb.core.internal.metadatatable.MetaDataTable;
+import org.flywaydb.core.internal.util.PlaceholderReplacer;
+import org.flywaydb.core.internal.util.jdbc.TransactionCallback;
+import org.flywaydb.core.internal.util.jdbc.TransactionTemplate;
+import org.flywaydb.core.internal.util.logging.Log;
+import org.flywaydb.core.internal.util.logging.LogFactory;
+import org.flywaydb.core.internal.util.scanner.filesystem.FileSystemResource;
+
+public class DbMigrateWithDryRun extends DbMigrate {
+
+ private static final Log LOG = LogFactory.getLog(DbMigrateWithDryRun.class);
+
+ private final List<SqlStatement> sqlStatements;
+ private final PlaceholderReplacer placeholderReplacer;
+ private final String encoding;
+ private final MigrationVersion target;
+ private final DbSupport dbSupport;
+ private final MetaDataTable metaDataTableForDryRun;
+ private final Schema schema;
+ private final MigrationResolver migrationResolver;
+ private final Connection connectionMetaDataTable;
+ private final Connection connectionUserObjects;
+ private final boolean outOfOrder;
+ private final FlywayCallback[] callbacks;
+ private final DbSupport dbSupportUserObjects;
+
+ /**
+ * Creates a new database migrator.
+ *
+ * @param sqlStatements The current list of all pending migrations.
+ * @param placeholderReplacer The placeholder replacer to apply to sql migration scripts.
+ * @param encoding The encoding of Sql migrations.
+ * @param metaDataDBTable The database metadata DB Table.
+ * @param connectionMetaDataTable The connection to use.
+ * @param connectionUserObjects The connection to use to perform the actual database migrations.
+ * @param dbSupport Database-specific functionality.
+ * @param metaDataTable The database metadata table.
+ * @param migrationResolver The migration resolver.
+ * @param target The target version of the migration.
+ * @param ignoreFutureMigrations Flag whether to ignore future migrations or not.
+ * @param ignoreFailedFutureMigration Flag whether to ignore failed future migrations or not.
+ * @param outOfOrder Allows migrations to be run "out of order".
+ */
+ public DbMigrateWithDryRun(final List<SqlStatement> sqlStatements,
+ final PlaceholderReplacer placeholderReplacer,
+ final String encoding,
+ final Table metaDataDBTable,
+ final Connection connectionMetaDataTable,
+ final Connection connectionUserObjects,
+ final DbSupport dbSupport,
+ final MetaDataTable metaDataTable,
+ final Schema schema,
+ final MigrationResolver migrationResolver,
+ final MigrationVersion target,
+ final boolean ignoreFutureMigrations,
+ final boolean ignoreFailedFutureMigration,
+ final boolean outOfOrder,
+ final FlywayCallback[] callbacks) {
+ super(connectionMetaDataTable, connectionUserObjects, dbSupport, metaDataTable, schema, migrationResolver, target, ignoreFutureMigrations, ignoreFailedFutureMigration, outOfOrder, callbacks);
+ this.sqlStatements = sqlStatements;
+ this.placeholderReplacer = placeholderReplacer;
+ this.encoding = encoding;
+ this.connectionMetaDataTable = connectionMetaDataTable;
+ this.connectionUserObjects = connectionUserObjects;
+ this.dbSupport = dbSupport;
+ this.schema = schema;
+ this.migrationResolver = migrationResolver;
+ this.target = target;
+ this.outOfOrder = outOfOrder;
+ this.callbacks = callbacks;
+
+ this.dbSupportUserObjects = DbSupportFactory.createDbSupport(connectionUserObjects, false);
+
+ // PIERRE: change MetaDataTable to capture the SQL
+ this.metaDataTableForDryRun = new CapturingMetaDataTable(sqlStatements, dbSupport, metaDataDBTable);
+ }
+
+ public int dryRunMigrate() throws FlywayException {
+ try {
+ for (final FlywayCallback callback : callbacks) {
+ new TransactionTemplate(connectionUserObjects).execute(new TransactionCallback<Object>() {
+ @Override
+ public Object doInTransaction() throws SQLException {
+ dbSupportUserObjects.changeCurrentSchemaTo(schema);
+ callback.beforeMigrate(connectionUserObjects);
+ return null;
+ }
+ });
+ }
+
+ // PIERRE: perform a single query to the metadata table
+ final MigrationInfoServiceImpl infoService = new MigrationInfoServiceImpl(migrationResolver, metaDataTableForDryRun, target, outOfOrder, true, true);
+ infoService.refresh();
+
+ final MigrationInfoImpl[] pendingMigrations = infoService.pending();
+ new TransactionTemplate(connectionMetaDataTable, false).execute(new TransactionCallback<Boolean>() {
+ public Boolean doInTransaction() {
+ int i = 1;
+ for (final MigrationInfoImpl migrationInfo : pendingMigrations) {
+ applyMigration(i, migrationInfo);
+ i++;
+ }
+
+ return true;
+ }
+ });
+
+ for (final FlywayCallback callback : callbacks) {
+ new TransactionTemplate(connectionUserObjects).execute(new TransactionCallback<Object>() {
+ @Override
+ public Object doInTransaction() throws SQLException {
+ dbSupportUserObjects.changeCurrentSchemaTo(schema);
+ callback.afterMigrate(connectionUserObjects);
+ return null;
+ }
+ });
+ }
+
+ return pendingMigrations.length;
+ } finally {
+ dbSupportUserObjects.restoreCurrentSchema();
+ }
+ }
+
+ private void applyMigration(final int installedRnk, final MigrationInfoImpl migration) {
+ final MigrationVersion version = migration.getVersion();
+ final String migrationText;
+ if (version != null) {
+ migrationText = "schema " + schema + " to version " + version + " - " + migration.getDescription();
+ } else {
+ migrationText = "schema " + schema + " with repeatable migration " + migration.getDescription();
+ }
+ LOG.info("Migrating " + migrationText);
+
+ // PIERRE: override the executor to capture the SQL
+ final FileSystemResource sqlScriptResource = new FileSystemResource(migration.getResolvedMigration().getPhysicalLocation());
+ final MigrationExecutor migrationExecutor = new CapturingSqlMigrationExecutor(sqlStatements,
+ dbSupport,
+ sqlScriptResource,
+ placeholderReplacer,
+ encoding);
+ try {
+ doMigrate(migration, migrationExecutor, migrationText);
+ } catch (final SQLException e) {
+ throw new FlywayException("Unable to apply migration", e);
+ }
+
+ final AppliedMigration appliedMigration = new AppliedMigration(installedRnk,
+ version,
+ migration.getDescription(),
+ migration.getType(),
+ migration.getScript(),
+ migration.getResolvedMigration().getChecksum(),
+ null,
+ null,
+ -1,
+ true);
+ metaDataTableForDryRun.addAppliedMigration(appliedMigration);
+ }
+
+ private void doMigrate(final MigrationInfo migration, final MigrationExecutor migrationExecutor, final String migrationText) throws SQLException {
+ for (final FlywayCallback callback : callbacks) {
+ dbSupportUserObjects.changeCurrentSchemaTo(schema);
+ callback.beforeEachMigrate(connectionUserObjects, migration);
+ }
+
+ dbSupportUserObjects.changeCurrentSchemaTo(schema);
+ migrationExecutor.execute(connectionUserObjects);
+ LOG.debug("Successfully completed migration of " + migrationText);
+
+ for (final FlywayCallback callback : callbacks) {
+ dbSupportUserObjects.changeCurrentSchemaTo(schema);
+ callback.afterEachMigrate(connectionUserObjects, migration);
+ }
+ }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/migration/Migrator.java b/util/src/test/java/org/killbill/billing/util/migration/Migrator.java
new file mode 100644
index 0000000..db378c8
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/migration/Migrator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.migration;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.flywaydb.core.FlywayWithDryRun;
+import org.flywaydb.core.internal.dbsupport.SqlStatement;
+
+public class Migrator {
+
+ public static void main(final String[] args) {
+ final List<SqlStatement> sqlStatements = new LinkedList<SqlStatement>();
+
+ final FlywayWithDryRun flyway = new FlywayWithDryRun(sqlStatements);
+ flyway.configure(System.getProperties());
+
+ flyway.dryRunMigrate();
+ // Flush logs
+ System.out.flush();
+
+ if (sqlStatements.isEmpty()) {
+ return;
+ }
+
+ final StringBuilder stringBuffer = new StringBuilder("BEGIN;\n");
+ for (final SqlStatement sqlStatement : sqlStatements) {
+ stringBuffer.append(sqlStatement.getSql())
+ .append(";\n");
+ }
+ stringBuffer.append("COMMIT;");
+ System.out.println(stringBuffer.toString());
+ }
+}