killbill-memoizeit

util: Flyway utility to dump SQL for migrations Relates to:

3/24/2016 8:09:08 PM

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());
+    }
+}