Java tutorial
/** * Copyright (C) 2010-2012 the original author or authors. * * Licensed 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 com.googlecode.flyway.core.metadatatable; import com.googlecode.flyway.core.dbsupport.DbSupport; import com.googlecode.flyway.core.exception.FlywayException; import com.googlecode.flyway.core.migration.MigrationState; import com.googlecode.flyway.core.migration.MigrationType; import com.googlecode.flyway.core.migration.SchemaVersion; import com.googlecode.flyway.core.migration.sql.PlaceholderReplacer; import com.googlecode.flyway.core.migration.sql.SqlScript; import com.googlecode.flyway.core.util.ClassPathResource; import com.googlecode.flyway.core.util.jdbc.JdbcTemplate; import com.googlecode.flyway.core.util.jdbc.RowMapper; import com.googlecode.flyway.core.util.jdbc.TransactionCallback; import com.googlecode.flyway.core.util.jdbc.TransactionTemplate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Supports reading and writing to the metadata table. */ public class MetaDataTable { /** * Logger. */ private static final Log LOG = LogFactory.getLog(MetaDataTable.class); /** * Database-specific functionality. */ private final DbSupport dbSupport; /** * The schema in which the metadata table belongs. */ private final String schema; /** * The name of the schema metadata table used by flyway. */ private final String table; /** * Connection with ddl manipulation access to the database. */ private final Connection connection; /** * JdbcTemplate with ddl manipulation access to the database. */ private final JdbcTemplate jdbcTemplate; /** * Creates a new instance of the metadata table support. * * @param connection Connection with ddl manipulation access to the database. * @param dbSupport Database-specific functionality. * @param schema The schema in which the metadata table belongs. * @param table The name of the schema metadata table used by flyway. */ public MetaDataTable(Connection connection, DbSupport dbSupport, String schema, String table) { this.connection = connection; this.jdbcTemplate = dbSupport.getJdbcTemplate(); this.dbSupport = dbSupport; this.schema = schema; this.table = table; } /** * Checks whether Flyway's metadata table is already present in the database. * * @return {@code true} if the table exists, {@code false} if it doesn't. */ private boolean exists() { try { return dbSupport.tableExists(schema, table); } catch (SQLException e) { throw new FlywayException( "Error checking whether table '" + table + "' exists in schema '" + schema + "'", e); } } /** * Creates Flyway's metadata table. */ private void create() { final String createMetaDataTableScriptSource = new ClassPathResource( dbSupport.getScriptLocation() + "createMetaDataTable.sql").loadAsString("UTF-8"); Map<String, String> placeholders = new HashMap<String, String>(); placeholders.put("schema", schema); placeholders.put("table", table); final PlaceholderReplacer placeholderReplacer = new PlaceholderReplacer(placeholders, "${", "}"); new TransactionTemplate(connection).execute(new TransactionCallback<Void>() { public Void doInTransaction() { SqlScript sqlScript = new SqlScript(createMetaDataTableScriptSource, placeholderReplacer); sqlScript.execute(jdbcTemplate); return null; } }); LOG.info("Metadata table created: " + table + " (Schema: " + schema + ")"); } /** * Creates the metadata table if it doesn't already exist. */ public void createIfNotExists() { if (!exists()) { create(); } } /** * Acquires an exclusive read-write lock on the metadata table. This lock will be released automatically on commit. */ public void lock() { try { dbSupport.lockTable(schema, table); } catch (SQLException e) { throw new FlywayException("Unable to lock metadata table '" + table + "' in schema '" + schema + "'", e); } } /** * Adds this row to the metadata table and mark it as current. * * @param metaDataTableRow The metaDataTableRow to add. */ public void insert(final MetaDataTableRow metaDataTableRow) { try { jdbcTemplate.update( "UPDATE " + schema + "." + table + " SET current_version=" + dbSupport.getBooleanFalse()); final String version = metaDataTableRow.getVersion().toString(); final String description = metaDataTableRow.getDescription(); final String state = metaDataTableRow.getState().name(); final String migrationType = metaDataTableRow.getMigrationType().name(); final Integer checksum = metaDataTableRow.getChecksum(); final String scriptName = metaDataTableRow.getScript(); final Integer executionTime = metaDataTableRow.getExecutionTime(); jdbcTemplate.update("INSERT INTO " + schema + "." + table + " (version, description, type, script, checksum, installed_by, execution_time, state, current_version)" + " VALUES (?, ?, ?, ?, ?, " + dbSupport.getCurrentUserFunction() + ", ?, ?, " + dbSupport.getBooleanTrue() + ")", version, description, migrationType, scriptName, checksum, executionTime, state); } catch (SQLException e) { throw new FlywayException( "Unable to insert metadata table row for version " + metaDataTableRow.getVersion().toString(), e); } } /** * Checks whether the metadata table contains at least one row. * * @return {@code true} if the metadata table has at least one row. {@code false} if it is empty or it doesn't exist * yet. */ private boolean hasRows() { if (!exists()) { return false; } try { return jdbcTemplate.queryForInt("SELECT COUNT(*) FROM " + schema + "." + table) > 0; } catch (SQLException e) { throw new FlywayException("Error checking if the metadata table has at least one row", e); } } /** * @return The latest migration applied on the schema. {@code null} if no migration has been applied so far. */ public MetaDataTableRow latestAppliedMigration() { if (!hasRows()) { return null; } String query = getSelectStatement() + " where current_version=" + dbSupport.getBooleanTrue(); try { final List<MetaDataTableRow> metaDataTableRows = jdbcTemplate.query(query, new MetaDataTableRowMapper()); if (metaDataTableRows.isEmpty()) { throw new FlywayException( "Cannot determine latest applied migration. Was the metadata table manually modified?"); } return metaDataTableRows.get(0); } catch (SQLException e) { throw new FlywayException("Error determining latest applied migration", e); } } /** * @return The list of all migrations applied on the schema (oldest first). An empty list if no migration has been * applied so far. */ public List<MetaDataTableRow> allAppliedMigrations() { if (!exists()) { return new ArrayList<MetaDataTableRow>(); } String query = getSelectStatement(); try { final List<MetaDataTableRow> metaDataTableRows = jdbcTemplate.query(query, new MetaDataTableRowMapper()); Collections.sort(metaDataTableRows); return metaDataTableRows; } catch (SQLException e) { throw new FlywayException("Error while retrieving the list of applied migrations", e); } } /** * @return The select statement for reading the metadata table. */ private String getSelectStatement() { return "select version as VERSION, description as DESCRIPTION, type as TYPE, script as SCRIPT, checksum as CHECKSUM, installed_on as INSTALLED_ON, execution_time as EXECUTION_TIME, state as STATE from " + schema + "." + table; } /** * Converts this number into an Integer. * * @param number The Number to convert. * @return The matching Integer. */ private Integer toInteger(Number number) { if (number == null) { return null; } return number.intValue(); } /** * @return The current state of the schema. */ public MigrationState getCurrentSchemaState() { MetaDataTableRow latestAppliedMigration = latestAppliedMigration(); if (latestAppliedMigration == null) { return MigrationState.SUCCESS; } return latestAppliedMigration.getState(); } /** * @return The current version of the schema. */ public SchemaVersion getCurrentSchemaVersion() { MetaDataTableRow latestAppliedMigration = latestAppliedMigration(); if (latestAppliedMigration == null) { return SchemaVersion.EMPTY; } return latestAppliedMigration.getVersion(); } /** * Row mapper for Migrations. */ private class MetaDataTableRowMapper implements RowMapper<MetaDataTableRow> { public MetaDataTableRow mapRow(final ResultSet rs) throws SQLException { SchemaVersion version = new SchemaVersion(rs.getString("VERSION")); String description = rs.getString("DESCRIPTION"); MigrationType migrationType = MigrationType.valueOf(rs.getString("TYPE")); String script = rs.getString("SCRIPT"); Integer checksum = toInteger((Number) rs.getObject("CHECKSUM")); Date installedOn = rs.getTimestamp("INSTALLED_ON"); Integer executionTime = toInteger((Number) rs.getObject("EXECUTION_TIME")); MigrationState migrationState = MigrationState.valueOf(rs.getString("STATE")); return new MetaDataTableRow(version, description, migrationType, script, checksum, installedOn, executionTime, migrationState); } } }