277 lines
10 KiB
Java
277 lines
10 KiB
Java
//
|
|
// $Id$
|
|
//
|
|
// Depot library - a Java relational persistence library
|
|
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
|
|
//
|
|
// This library is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU Lesser General Public License as published
|
|
// by the Free Software Foundation; either version 2.1 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
package com.samskivert.depot;
|
|
|
|
import java.sql.Connection;
|
|
import java.sql.SQLException;
|
|
import java.util.Map;
|
|
|
|
import com.samskivert.jdbc.ColumnDefinition;
|
|
import com.samskivert.jdbc.DatabaseLiaison;
|
|
|
|
import com.samskivert.depot.annotation.Column;
|
|
import com.samskivert.depot.expression.ColumnExp;
|
|
import com.samskivert.depot.impl.FieldMarshaller;
|
|
import com.samskivert.depot.impl.Modifier;
|
|
|
|
import static com.samskivert.depot.Log.log;
|
|
|
|
/**
|
|
* Encapsulates the migration of a persistent record's database schema. These can be registered
|
|
* with the {@link PersistenceContext} to effect hand-coded migrations between entity versions. The
|
|
* modifier should override {@link #invoke} to perform its migrations. See {@link
|
|
* PersistenceContext#registerMigration} for details on the migration process.
|
|
*
|
|
* <p> Note: these should only be used for actual schema changes (column additions, removals,
|
|
* renames, retypes, etc.). It should not be used for data migration, use {@link DataMigration} for
|
|
* that.
|
|
*/
|
|
public abstract class SchemaMigration extends Modifier
|
|
{
|
|
/**
|
|
* A convenient migration for dropping a column from an entity.
|
|
*/
|
|
public static class Drop extends SchemaMigration
|
|
{
|
|
public Drop (int targetVersion, String columnName) {
|
|
super(targetVersion);
|
|
_columnName = columnName;
|
|
}
|
|
|
|
@Override
|
|
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
|
|
if (!liaison.tableContainsColumn(conn, _tableName, _columnName)) {
|
|
// we'll accept this inconsistency
|
|
log.warning(_tableName + "." + _columnName + " already dropped.");
|
|
return 0;
|
|
}
|
|
|
|
log.info("Dropping '" + _columnName + "' from " + _tableName);
|
|
return liaison.dropColumn(conn, _tableName, _columnName) ? 1 : 0;
|
|
}
|
|
|
|
protected String _columnName;
|
|
}
|
|
|
|
/**
|
|
* A convenient migration for renaming a column in an entity.
|
|
*/
|
|
public static class Rename extends SchemaMigration
|
|
{
|
|
public Rename (int targetVersion, String oldColumnName, ColumnExp newColumn) {
|
|
super(targetVersion);
|
|
_oldColumnName = oldColumnName;
|
|
_newColumnName = newColumn.name;
|
|
}
|
|
|
|
@Override public boolean runBeforeDefault () {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
|
|
super.init(tableName, marshallers);
|
|
FieldMarshaller<?> fm = marshallers.get(_newColumnName);
|
|
if (fm == null) {
|
|
throw new IllegalArgumentException(
|
|
_tableName + " does not contain '" + _newColumnName + "' field.");
|
|
}
|
|
_newColumnDef = fm.getColumnDefinition();
|
|
}
|
|
|
|
@Override
|
|
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
|
|
if (!liaison.tableContainsColumn(conn, _tableName, _oldColumnName)) {
|
|
if (liaison.tableContainsColumn(conn, _tableName, _newColumnName)) {
|
|
// we'll accept this inconsistency
|
|
log.warning(_tableName + "." + _oldColumnName + " already renamed to " +
|
|
_newColumnName + ".");
|
|
return 0;
|
|
}
|
|
// but this is not OK
|
|
throw new IllegalArgumentException(
|
|
_tableName + " does not contain '" + _oldColumnName + "'");
|
|
}
|
|
|
|
// nor is this
|
|
if (liaison.tableContainsColumn(conn, _tableName, _newColumnName)) {
|
|
throw new IllegalArgumentException(
|
|
_tableName + " already contains '" + _newColumnName + "'");
|
|
}
|
|
|
|
log.info("Renaming '" + _oldColumnName + "' to '" + _newColumnName + "' in: " +
|
|
_tableName);
|
|
return liaison.renameColumn(
|
|
conn, _tableName, _oldColumnName, _newColumnName, _newColumnDef) ? 1 : 0;
|
|
}
|
|
|
|
protected String _oldColumnName, _newColumnName;
|
|
protected ColumnDefinition _newColumnDef;
|
|
}
|
|
|
|
/**
|
|
* A convenient migration for changing the type of an existing field. NOTE: This object is
|
|
* instantiated with the name of a persistent field, not the name of a database column. These
|
|
* can be very different things for classes that use @Column annotations.
|
|
*/
|
|
public static class Retype extends SchemaMigration
|
|
{
|
|
public Retype (int targetVersion, ColumnExp column) {
|
|
super(targetVersion);
|
|
_fieldName = column.name;
|
|
}
|
|
|
|
@Override public boolean runBeforeDefault () {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
|
|
super.init(tableName, marshallers);
|
|
_columnName = marshallers.get(_fieldName).getColumnName();
|
|
_newColumnDef = marshallers.get(_fieldName).getColumnDefinition();
|
|
}
|
|
|
|
@Override
|
|
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
|
|
log.info("Updating type of '" + _fieldName + "' in " + _tableName);
|
|
return liaison.changeColumn(conn, _tableName, _fieldName, _newColumnDef.type,
|
|
_newColumnDef.nullable, _newColumnDef.unique,
|
|
_newColumnDef.defaultValue) ? 1 : 0;
|
|
}
|
|
|
|
protected String _fieldName, _columnName;
|
|
protected ColumnDefinition _newColumnDef;
|
|
}
|
|
|
|
/**
|
|
* A convenient migration for adding a new column that requires a default value to be specified
|
|
* during the addition. Normally Depot will automatically handle column addition, but if you
|
|
* have a column that normally does not have a default value but needs one when it is added to
|
|
* a table with existing rows, you can use this migration.
|
|
*
|
|
* @see Column#defaultValue
|
|
*/
|
|
public static class Add extends SchemaMigration
|
|
{
|
|
public Add (int targetVersion, ColumnExp column, String defaultValue) {
|
|
super(targetVersion);
|
|
_fieldName = column.name;
|
|
_defaultValue = defaultValue;
|
|
}
|
|
|
|
@Override public boolean runBeforeDefault () {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
|
|
super.init(tableName, marshallers);
|
|
_columnName = marshallers.get(_fieldName).getColumnName();
|
|
_newColumnDef = marshallers.get(_fieldName).getColumnDefinition();
|
|
}
|
|
|
|
@Override
|
|
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
|
|
// override the default value in the column definition with the one provided
|
|
ColumnDefinition defColumnDef = new ColumnDefinition(
|
|
_newColumnDef.type, _newColumnDef.nullable,
|
|
_newColumnDef.unique, _defaultValue);
|
|
// first add the column with the overridden default value
|
|
if (liaison.addColumn(conn, _tableName, _fieldName, defColumnDef, true)) {
|
|
// then change the column to the permanent default value
|
|
liaison.changeColumn(conn, _tableName, _fieldName, _newColumnDef.type,
|
|
null, null, _newColumnDef.defaultValue);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
protected String _fieldName, _columnName, _defaultValue;
|
|
protected ColumnDefinition _newColumnDef;
|
|
}
|
|
|
|
/**
|
|
* A convenient migration for dropping a column from an entity.
|
|
*/
|
|
public static class DropIndex extends SchemaMigration
|
|
{
|
|
public DropIndex (int targetVersion, String ixName) {
|
|
super(targetVersion);
|
|
_ixName = ixName;
|
|
}
|
|
|
|
@Override
|
|
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
|
|
if (!liaison.tableContainsIndex(conn, _tableName, _ixName)) {
|
|
// we'll accept this inconsistency
|
|
log.warning(_tableName + " index '" + _ixName + "' does not exist.");
|
|
return 0;
|
|
}
|
|
|
|
log.info("Dropping index '" + _ixName + "' from " + _tableName);
|
|
liaison.dropIndex(conn, _tableName, _ixName);
|
|
return 1;
|
|
}
|
|
|
|
protected String _ixName;
|
|
}
|
|
|
|
/**
|
|
* If this method returns true, this migration will be run <b>before</b> the default
|
|
* migrations, if false it will be run after.
|
|
*/
|
|
public boolean runBeforeDefault ()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* When an Entity is being migrated, this method will be called to check whether this migration
|
|
* should be run. The default implementation runs as long as the currentVersion is less than
|
|
* the target version supplied to the migration at construct time.
|
|
*/
|
|
public boolean shouldRunMigration (int currentVersion, int targetVersion)
|
|
{
|
|
return (currentVersion < _targetVersion);
|
|
}
|
|
|
|
/**
|
|
* This is called to provide the migration with the name of the entity table and access to its
|
|
* field marshallers prior to being invoked. This will <em>only</em> be called after this
|
|
* migration has been determined to be runnable so one cannot rely on this method having been
|
|
* called in {@link #shouldRunMigration}.
|
|
*/
|
|
public void init (String tableName, Map<String, FieldMarshaller<?>> marshallers)
|
|
{
|
|
_tableName = tableName;
|
|
}
|
|
|
|
protected SchemaMigration (int targetVersion)
|
|
{
|
|
super();
|
|
_targetVersion = targetVersion;
|
|
}
|
|
|
|
protected int _targetVersion;
|
|
protected String _tableName;
|
|
}
|