package org.apache.ddlutils.alteration; | |
/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF 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. | |
*/ | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import org.apache.commons.logging.Log; | |
import org.apache.commons.logging.LogFactory; | |
import org.apache.ddlutils.PlatformInfo; | |
import org.apache.ddlutils.model.CloneHelper; | |
import org.apache.ddlutils.model.Column; | |
import org.apache.ddlutils.model.Database; | |
import org.apache.ddlutils.model.ForeignKey; | |
import org.apache.ddlutils.model.Index; | |
import org.apache.ddlutils.model.Table; | |
import org.apache.ddlutils.util.StringUtilsExt; | |
/** | |
* Compares two database models and creates change objects that express how to | |
* adapt the first model so that it becomes the second one. Neither of the models | |
* are changed in the process, however, it is also assumed that the models do not | |
* change in between. | |
* | |
* @version $Revision: $ | |
*/ | |
public class ModelComparator | |
{ | |
/** The log for this comparator. */ | |
private final Log _log = LogFactory.getLog(ModelComparator.class); | |
/** The platform information. */ | |
private PlatformInfo _platformInfo; | |
/** The predicate that defines which changes are supported by the platform. */ | |
private TableDefinitionChangesPredicate _tableDefCangePredicate; | |
/** The object clone helper. */ | |
private CloneHelper _cloneHelper = new CloneHelper(); | |
/** Whether comparison is case sensitive. */ | |
private boolean _caseSensitive; | |
/** Whether the comparator should generate {@link PrimaryKeyChange} objects. */ | |
private boolean _generatePrimaryKeyChanges = true; | |
/** Whether {@link RemoveColumnChange} objects for primary key columns are enough or | |
additional primary key change objects are necessary. */ | |
private boolean _canDropPrimaryKeyColumns = true; | |
/** | |
* Creates a new model comparator object. | |
* | |
* @param platformInfo The platform info | |
* @param tableDefChangePredicate The predicate that defines whether tables changes are supported | |
* by the platform or not; all changes are supported if this is null | |
* @param caseSensitive Whether comparison is case sensitive | |
*/ | |
public ModelComparator(PlatformInfo platformInfo, | |
TableDefinitionChangesPredicate tableDefChangePredicate, | |
boolean caseSensitive) | |
{ | |
_platformInfo = platformInfo; | |
_caseSensitive = caseSensitive; | |
_tableDefCangePredicate = tableDefChangePredicate; | |
} | |
/** | |
* Specifies whether the comparator should generate {@link PrimaryKeyChange} objects or a | |
* pair of {@link RemovePrimaryKeyChange} and {@link AddPrimaryKeyChange} objects instead. | |
* The default value is <code>true</code>. | |
* | |
* @param generatePrimaryKeyChanges Whether to create {@link PrimaryKeyChange} objects | |
*/ | |
public void setGeneratePrimaryKeyChanges(boolean generatePrimaryKeyChanges) | |
{ | |
_generatePrimaryKeyChanges = generatePrimaryKeyChanges; | |
} | |
/** | |
* Specifies whether the {@link RemoveColumnChange} are fine even for primary key columns. | |
* If the platform cannot drop primary key columns, set this to <code>false</code> and the | |
* comparator will create additional primary key changes. | |
* The default value is <code>true</code>. | |
* | |
* @param canDropPrimaryKeyColumns Whether {@link RemoveColumnChange} objecs for primary | |
* key columns are ok | |
*/ | |
public void setCanDropPrimaryKeyColumns(boolean canDropPrimaryKeyColumns) | |
{ | |
_canDropPrimaryKeyColumns = canDropPrimaryKeyColumns; | |
} | |
/** | |
* Returns the info object for the platform. | |
* | |
* @return The platform info object | |
*/ | |
protected PlatformInfo getPlatformInfo() | |
{ | |
return _platformInfo; | |
} | |
/** | |
* Determines whether comparison should be case sensitive. | |
* | |
* @return <code>true</code> if case matters | |
*/ | |
protected boolean isCaseSensitive() | |
{ | |
return _caseSensitive; | |
} | |
/** | |
* Compares the two models and returns the changes necessary to create the second | |
* model from the first one. | |
* | |
* @param sourceModel The source model | |
* @param targetModel The target model | |
* @return The changes | |
*/ | |
public List compare(Database sourceModel, Database targetModel) | |
{ | |
Database intermediateModel = _cloneHelper.clone(sourceModel); | |
return compareModels(sourceModel, intermediateModel, targetModel); | |
} | |
/** | |
* Compares the given source and target models and creates change objects to get from | |
* the source to the target one. These changes will be applied to the given | |
* intermediate model (the other two won't be changed), so that it will be equal to | |
* the target model after this model has finished. | |
* | |
* @param sourceModel The source model | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param targetModel The target model | |
* @return The changes | |
*/ | |
protected List compareModels(Database sourceModel, | |
Database intermediateModel, | |
Database targetModel) | |
{ | |
ArrayList changes = new ArrayList(); | |
changes.addAll(checkForRemovedForeignKeys(sourceModel, intermediateModel, targetModel)); | |
changes.addAll(checkForRemovedTables(sourceModel, intermediateModel, targetModel)); | |
for (int tableIdx = 0; tableIdx < intermediateModel.getTableCount(); tableIdx++) | |
{ | |
Table intermediateTable = intermediateModel.getTable(tableIdx); | |
Table sourceTable = sourceModel.findTable(intermediateTable.getName(), _caseSensitive); | |
Table targetTable = targetModel.findTable(intermediateTable.getName(), _caseSensitive); | |
List tableChanges = compareTables(sourceModel, sourceTable, | |
intermediateModel, intermediateTable, | |
targetModel, targetTable); | |
changes.addAll(tableChanges); | |
} | |
changes.addAll(checkForAddedTables(sourceModel, intermediateModel, targetModel)); | |
changes.addAll(checkForAddedForeignKeys(sourceModel, intermediateModel, targetModel)); | |
return changes; | |
} | |
/** | |
* Creates change objects for foreign keys that are present in the given source model but are no longer in the target | |
* model, and applies them to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param targetModel The target model | |
* @return The changes | |
*/ | |
protected List checkForRemovedForeignKeys(Database sourceModel, | |
Database intermediateModel, | |
Database targetModel) | |
{ | |
List changes = new ArrayList(); | |
for (int tableIdx = 0; tableIdx < intermediateModel.getTableCount(); tableIdx++) | |
{ | |
Table intermediateTable = intermediateModel.getTable(tableIdx); | |
Table targetTable = targetModel.findTable(intermediateTable.getName(), _caseSensitive); | |
ForeignKey[] intermediateFks = intermediateTable.getForeignKeys(); | |
// Dropping foreign keys from tables to be removed might not be necessary, but some databases might require it | |
for (int fkIdx = 0; fkIdx < intermediateFks.length; fkIdx++) | |
{ | |
ForeignKey sourceFk = intermediateFks[fkIdx]; | |
ForeignKey targetFk = targetTable == null ? null : findCorrespondingForeignKey(targetTable, sourceFk); | |
if (targetFk == null) | |
{ | |
if (_log.isInfoEnabled()) | |
{ | |
_log.info("Foreign key " + sourceFk + " needs to be removed from table " + intermediateTable.getName()); | |
} | |
RemoveForeignKeyChange fkChange = new RemoveForeignKeyChange(intermediateTable.getName(), sourceFk); | |
changes.add(fkChange); | |
fkChange.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
} | |
return changes; | |
} | |
/** | |
* Creates change objects for foreign keys that are not present in the given source model but are in the target | |
* model, and applies them to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param targetModel The target model | |
* @return The changes | |
*/ | |
protected List checkForAddedForeignKeys(Database sourceModel, | |
Database intermediateModel, | |
Database targetModel) | |
{ | |
List changes = new ArrayList(); | |
for (int tableIdx = 0; tableIdx < targetModel.getTableCount(); tableIdx++) | |
{ | |
Table targetTable = targetModel.getTable(tableIdx); | |
Table intermediateTable = intermediateModel.findTable(targetTable.getName(), _caseSensitive); | |
for (int fkIdx = 0; fkIdx < targetTable.getForeignKeyCount(); fkIdx++) | |
{ | |
ForeignKey targetFk = targetTable.getForeignKey(fkIdx); | |
ForeignKey intermediateFk = findCorrespondingForeignKey(intermediateTable, targetFk); | |
if (intermediateFk == null) | |
{ | |
if (_log.isInfoEnabled()) | |
{ | |
_log.info("Foreign key " + targetFk + " needs to be added to table " + intermediateTable.getName()); | |
} | |
intermediateFk = _cloneHelper.clone(targetFk, intermediateTable, intermediateModel, _caseSensitive); | |
AddForeignKeyChange fkChange = new AddForeignKeyChange(intermediateTable.getName(), intermediateFk); | |
changes.add(fkChange); | |
fkChange.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
} | |
return changes; | |
} | |
/** | |
* Creates change objects for tables that are present in the given source model but are no longer in the target | |
* model, and applies them to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param targetModel The target model | |
* @return The changes | |
*/ | |
protected List checkForRemovedTables(Database sourceModel, | |
Database intermediateModel, | |
Database targetModel) | |
{ | |
List changes = new ArrayList(); | |
Table[] intermediateTables = intermediateModel.getTables(); | |
for (int tableIdx = 0; tableIdx < intermediateTables.length; tableIdx++) | |
{ | |
Table intermediateTable = intermediateTables[tableIdx]; | |
Table targetTable = targetModel.findTable(intermediateTable.getName(), _caseSensitive); | |
if (targetTable == null) | |
{ | |
if (_log.isInfoEnabled()) | |
{ | |
_log.info("Table " + intermediateTable.getName() + " needs to be removed"); | |
} | |
RemoveTableChange tableChange = new RemoveTableChange(intermediateTable.getName()); | |
changes.add(tableChange); | |
tableChange.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
return changes; | |
} | |
/** | |
* Creates change objects for tables that are not present in the given source model but are in the target | |
* model, and applies them to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param targetModel The target model | |
* @return The changes | |
*/ | |
protected List checkForAddedTables(Database sourceModel, | |
Database intermediateModel, | |
Database targetModel) | |
{ | |
List changes = new ArrayList(); | |
for (int tableIdx = 0; tableIdx < targetModel.getTableCount(); tableIdx++) | |
{ | |
Table targetTable = targetModel.getTable(tableIdx); | |
Table intermediateTable = intermediateModel.findTable(targetTable.getName(), _caseSensitive); | |
if (intermediateTable == null) | |
{ | |
if (_log.isInfoEnabled()) | |
{ | |
_log.info("Table " + targetTable.getName() + " needs to be added"); | |
} | |
// we're using a clone of the target table, and remove all foreign | |
// keys as these will be added later | |
intermediateTable = _cloneHelper.clone(targetTable, true, false, intermediateModel, _caseSensitive); | |
AddTableChange tableChange = new AddTableChange(intermediateTable); | |
changes.add(tableChange); | |
tableChange.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
return changes; | |
} | |
/** | |
* Compares the two tables and returns the changes necessary to create the second | |
* table from the first one. | |
* | |
* @param sourceModel The source model | |
* @param sourceTable The source table | |
* @param intermediateModel The intermediate model to which the changes will be applied incrementally | |
* @param intermediateTable The table corresponding to the source table in the intermediate model | |
* @param targetModel The target model which contains the target table | |
* @param targetTable The target table | |
* @return The changes | |
*/ | |
protected List compareTables(Database sourceModel, | |
Table sourceTable, | |
Database intermediateModel, | |
Table intermediateTable, | |
Database targetModel, | |
Table targetTable) | |
{ | |
ArrayList changes = new ArrayList(); | |
changes.addAll(checkForRemovedIndexes(sourceModel, sourceTable, intermediateModel, intermediateTable, targetModel, targetTable)); | |
ArrayList tableDefinitionChanges = new ArrayList(); | |
Table tmpTable = _cloneHelper.clone(intermediateTable, true, false, intermediateModel, _caseSensitive); | |
tableDefinitionChanges.addAll(checkForRemovedColumns(sourceModel, sourceTable, intermediateModel, intermediateTable, targetModel, targetTable)); | |
tableDefinitionChanges.addAll(checkForChangeOfColumnOrder(sourceModel, sourceTable, intermediateModel, intermediateTable, targetModel, targetTable)); | |
tableDefinitionChanges.addAll(checkForChangedColumns(sourceModel, sourceTable, intermediateModel, intermediateTable, targetModel, targetTable)); | |
tableDefinitionChanges.addAll(checkForAddedColumns(sourceModel, sourceTable, intermediateModel, intermediateTable, targetModel, targetTable)); | |
tableDefinitionChanges.addAll(checkForPrimaryKeyChanges(sourceModel, sourceTable, intermediateModel, intermediateTable, targetModel, targetTable)); | |
// TOOD: check for foreign key changes (on delete/on update) | |
if (!tableDefinitionChanges.isEmpty()) | |
{ | |
if ((_tableDefCangePredicate == null) || _tableDefCangePredicate.areSupported(tmpTable, tableDefinitionChanges)) | |
{ | |
changes.addAll(tableDefinitionChanges); | |
} | |
else | |
{ | |
// we need to recreate the table; for this to work we need to remove foreign keys to and from the table | |
// however, we don't have to add them back here as there is a check for added foreign keys/indexes | |
// later on anyways | |
// we also don't have to drop indexes on the original table | |
ForeignKey[] fks = intermediateTable.getForeignKeys(); | |
for (int fkIdx = 0; fkIdx < fks.length; fkIdx++) | |
{ | |
RemoveForeignKeyChange fkChange = new RemoveForeignKeyChange(intermediateTable.getName(), fks[fkIdx]); | |
changes.add(fkChange); | |
fkChange.apply(intermediateModel, _caseSensitive); | |
} | |
for (int tableIdx = 0; tableIdx < intermediateModel.getTableCount(); tableIdx++) | |
{ | |
Table curTable = intermediateModel.getTable(tableIdx); | |
if (curTable != intermediateTable) | |
{ | |
ForeignKey[] curFks = curTable.getForeignKeys(); | |
for (int fkIdx = 0; fkIdx < curFks.length; fkIdx++) | |
{ | |
if ((_caseSensitive && curFks[fkIdx].getForeignTableName().equals(intermediateTable.getName())) || | |
(!_caseSensitive && curFks[fkIdx].getForeignTableName().equalsIgnoreCase(intermediateTable.getName()))) | |
{ | |
RemoveForeignKeyChange fkChange = new RemoveForeignKeyChange(curTable.getName(), curFks[fkIdx]); | |
changes.add(fkChange); | |
fkChange.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
} | |
} | |
RecreateTableChange tableChange = new RecreateTableChange(intermediateTable.getName(), | |
intermediateTable, | |
new ArrayList(tableDefinitionChanges)); | |
changes.add(tableChange); | |
tableChange.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
changes.addAll(checkForAddedIndexes(sourceModel, sourceTable, intermediateModel, intermediateTable, targetModel, targetTable)); | |
return changes; | |
} | |
/** | |
* Returns the names of the columns in the intermediate table corresponding to the given column objects. | |
* | |
* @param columns The column objects | |
* @param intermediateTable The intermediate table | |
* @return The column names | |
*/ | |
protected String[] getIntermediateColumnNamesFor(Column[] columns, Table intermediateTable) | |
{ | |
String[] result = new String[columns.length]; | |
for (int idx = 0; idx < columns.length; idx++) | |
{ | |
result[idx] = intermediateTable.findColumn(columns[idx].getName(), _caseSensitive).getName(); | |
} | |
return result; | |
} | |
/** | |
* Creates change objects for indexes that are present in the given source table but are no longer in the target | |
* table, and applies them to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param sourceTable The source table | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param intermediateTable The table from the intermediate model corresponding to the source table | |
* @param targetModel The target model | |
* @param targetTable The target table | |
* @return The changes | |
*/ | |
protected List checkForRemovedIndexes(Database sourceModel, | |
Table sourceTable, | |
Database intermediateModel, | |
Table intermediateTable, | |
Database targetModel, | |
Table targetTable) | |
{ | |
List changes = new ArrayList(); | |
Index[] indexes = intermediateTable.getIndices(); | |
for (int indexIdx = 0; indexIdx < indexes.length; indexIdx++) | |
{ | |
Index sourceIndex = indexes[indexIdx]; | |
Index targetIndex = findCorrespondingIndex(targetTable, sourceIndex); | |
if (targetIndex == null) | |
{ | |
if (_log.isInfoEnabled()) | |
{ | |
_log.info("Index " + sourceIndex.getName() + " needs to be removed from table " + intermediateTable.getName()); | |
} | |
RemoveIndexChange change = new RemoveIndexChange(intermediateTable.getName(), sourceIndex); | |
changes.add(change); | |
change.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
return changes; | |
} | |
/** | |
* Creates change objects for indexes that are not present in the given source table but are in the target | |
* table, and applies them to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param sourceTable The source table | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param intermediateTable The table from the intermediate model corresponding to the source table | |
* @param targetModel The target model | |
* @param targetTable The target table | |
* @return The changes | |
*/ | |
protected List checkForAddedIndexes(Database sourceModel, | |
Table sourceTable, | |
Database intermediateModel, | |
Table intermediateTable, | |
Database targetModel, | |
Table targetTable) | |
{ | |
List changes = new ArrayList(); | |
for (int indexIdx = 0; indexIdx < targetTable.getIndexCount(); indexIdx++) | |
{ | |
Index targetIndex = targetTable.getIndex(indexIdx); | |
Index intermediateIndex = findCorrespondingIndex(intermediateTable, targetIndex); | |
Index sourceIndex = findCorrespondingIndex(sourceTable, targetIndex); | |
if ((sourceIndex == null) && (intermediateIndex == null)) | |
{ | |
if (_log.isInfoEnabled()) | |
{ | |
_log.info("Index " + targetIndex.getName() + " needs to be created for table " + intermediateTable.getName()); | |
} | |
Index clonedIndex = _cloneHelper.clone(targetIndex, intermediateTable, _caseSensitive); | |
AddIndexChange change = new AddIndexChange(intermediateTable.getName(), clonedIndex); | |
changes.add(change); | |
change.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
return changes; | |
} | |
/** | |
* Checks for changes in the column order between the given source and target table, creates change objects for these and | |
* applies them to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param sourceTable The source table | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param intermediateTable The table from the intermediate model corresponding to the source table | |
* @param targetModel The target model | |
* @param targetTable The target table | |
* @return The changes | |
*/ | |
protected List checkForChangeOfColumnOrder(Database sourceModel, | |
Table sourceTable, | |
Database intermediateModel, | |
Table intermediateTable, | |
Database targetModel, | |
Table targetTable) | |
{ | |
List changes = new ArrayList(); | |
List targetOrder = new ArrayList(); | |
int numChangedPKs = 0; | |
for (int columnIdx = 0; columnIdx < targetTable.getColumnCount(); columnIdx++) | |
{ | |
Column targetColumn = targetTable.getColumn(columnIdx); | |
Column sourceColumn = intermediateTable.findColumn(targetColumn.getName(), _caseSensitive); | |
if (sourceColumn != null) | |
{ | |
targetOrder.add(sourceColumn); | |
} | |
} | |
HashMap newPositions = new HashMap(); | |
for (int columnIdx = 0; columnIdx < intermediateTable.getColumnCount(); columnIdx++) | |
{ | |
Column sourceColumn = intermediateTable.getColumn(columnIdx); | |
int targetIdx = targetOrder.indexOf(sourceColumn); | |
if ((targetIdx >= 0) && (targetIdx != columnIdx)) | |
{ | |
newPositions.put(sourceColumn.getName(), new Integer(targetIdx)); | |
if (sourceColumn.isPrimaryKey()) | |
{ | |
numChangedPKs++; | |
} | |
} | |
} | |
if (!newPositions.isEmpty()) | |
{ | |
ColumnOrderChange change = new ColumnOrderChange(intermediateTable.getName(), newPositions); | |
change.apply(intermediateModel, _caseSensitive); | |
if (numChangedPKs > 1) | |
{ | |
// create pk change that only covers the order change | |
// fortunately, the order change will have adjusted the pk order already | |
changes.add(new PrimaryKeyChange(intermediateTable.getName(), | |
getIntermediateColumnNamesFor(intermediateTable.getPrimaryKeyColumns(), intermediateTable))); | |
} | |
changes.add(change); | |
} | |
return changes; | |
} | |
/** | |
* Creates change objects for columns that are present in the given source table but are no longer in the target | |
* table, and applies them to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param sourceTable The source table | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param intermediateTable The table from the intermediate model corresponding to the source table | |
* @param targetModel The target model | |
* @param targetTable The target table | |
* @return The changes | |
*/ | |
protected List checkForRemovedColumns(Database sourceModel, | |
Table sourceTable, | |
Database intermediateModel, | |
Table intermediateTable, | |
Database targetModel, | |
Table targetTable) | |
{ | |
// if the platform does not support dropping pk columns, then the pk handling above will | |
// generate appropriate pk changes | |
List changes = new ArrayList(); | |
Column[] columns = intermediateTable.getColumns(); | |
for (int columnIdx = 0; columnIdx < columns.length; columnIdx++) | |
{ | |
Column sourceColumn = columns[columnIdx]; | |
Column targetColumn = targetTable.findColumn(sourceColumn.getName(), _caseSensitive); | |
if (targetColumn == null) | |
{ | |
if (_log.isInfoEnabled()) | |
{ | |
_log.info("Column " + sourceColumn.getName() + " needs to be removed from table " + intermediateTable.getName()); | |
} | |
RemoveColumnChange change = new RemoveColumnChange(intermediateTable.getName(), sourceColumn.getName()); | |
changes.add(change); | |
change.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
return changes; | |
} | |
/** | |
* Creates change objects for columns that are not present in the given source table but are in the target | |
* table, and applies them to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param sourceTable The source table | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param intermediateTable The table from the intermediate model corresponding to the source table | |
* @param targetModel The target model | |
* @param targetTable The target table | |
* @return The changes | |
*/ | |
protected List checkForAddedColumns(Database sourceModel, | |
Table sourceTable, | |
Database intermediateModel, | |
Table intermediateTable, | |
Database targetModel, | |
Table targetTable) | |
{ | |
List changes = new ArrayList(); | |
for (int columnIdx = 0; columnIdx < targetTable.getColumnCount(); columnIdx++) | |
{ | |
Column targetColumn = targetTable.getColumn(columnIdx); | |
Column sourceColumn = intermediateTable.findColumn(targetColumn.getName(), _caseSensitive); | |
if (sourceColumn == null) | |
{ | |
String prevColumn = (columnIdx > 0 ? intermediateTable.getColumn(columnIdx - 1).getName() : null); | |
String nextColumn = (columnIdx < intermediateTable.getColumnCount() ? intermediateTable.getColumn(columnIdx).getName() : null); | |
Column clonedColumn = _cloneHelper.clone(targetColumn, false); | |
AddColumnChange change = new AddColumnChange(intermediateTable.getName(), clonedColumn, prevColumn, nextColumn); | |
changes.add(change); | |
change.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
return changes; | |
} | |
/** | |
* Creates change objects for columns that have a different in the given source and target table, and applies them | |
* to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param sourceTable The source table | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param intermediateTable The table from the intermediate model corresponding to the source table | |
* @param targetModel The target model | |
* @param targetTable The target table | |
* @return The changes | |
*/ | |
protected List checkForChangedColumns(Database sourceModel, | |
Table sourceTable, | |
Database intermediateModel, | |
Table intermediateTable, | |
Database targetModel, | |
Table targetTable) | |
{ | |
List changes = new ArrayList(); | |
for (int columnIdx = 0; columnIdx < targetTable.getColumnCount(); columnIdx++) | |
{ | |
Column targetColumn = targetTable.getColumn(columnIdx); | |
Column sourceColumn = intermediateTable.findColumn(targetColumn.getName(), _caseSensitive); | |
if (sourceColumn != null) | |
{ | |
ColumnDefinitionChange change = compareColumns(intermediateTable, sourceColumn, targetTable, targetColumn); | |
if (change != null) | |
{ | |
changes.add(change); | |
change.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
} | |
return changes; | |
} | |
/** | |
* Creates change objects for primary key differences (primary key added/removed/changed), and applies them to the given intermediate model. | |
* | |
* @param sourceModel The source model | |
* @param sourceTable The source table | |
* @param intermediateModel The intermediate model to apply the changes to | |
* @param intermediateTable The table from the intermediate model corresponding to the source table | |
* @param targetModel The target model | |
* @param targetTable The target table | |
* @return The changes | |
*/ | |
protected List checkForPrimaryKeyChanges(Database sourceModel, | |
Table sourceTable, | |
Database intermediateModel, | |
Table intermediateTable, | |
Database targetModel, | |
Table targetTable) | |
{ | |
List changes = new ArrayList(); | |
Column[] sourcePK = sourceTable.getPrimaryKeyColumns(); | |
Column[] curPK = intermediateTable.getPrimaryKeyColumns(); | |
Column[] targetPK = targetTable.getPrimaryKeyColumns(); | |
if ((curPK.length == 0) && (targetPK.length > 0)) | |
{ | |
if (_log.isInfoEnabled()) | |
{ | |
_log.info("A primary key needs to be added to the table " + intermediateTable.getName()); | |
} | |
AddPrimaryKeyChange change = new AddPrimaryKeyChange(intermediateTable.getName(), getIntermediateColumnNamesFor(targetPK, intermediateTable)); | |
changes.add(change); | |
change.apply(intermediateModel, _caseSensitive); | |
} | |
else if ((targetPK.length == 0) && (curPK.length > 0)) | |
{ | |
if (_log.isInfoEnabled()) | |
{ | |
_log.info("The primary key needs to be removed from the table " + intermediateTable.getName()); | |
} | |
RemovePrimaryKeyChange change = new RemovePrimaryKeyChange(intermediateTable.getName()); | |
changes.add(change); | |
change.apply(intermediateModel, _caseSensitive); | |
} | |
else | |
{ | |
boolean changePK = false; | |
if ((curPK.length != targetPK.length) || (!_canDropPrimaryKeyColumns && sourcePK.length > targetPK.length)) | |
{ | |
changePK = true; | |
} | |
else if ((curPK.length > 0) && (targetPK.length > 0)) | |
{ | |
for (int pkColumnIdx = 0; (pkColumnIdx < curPK.length) && !changePK; pkColumnIdx++) | |
{ | |
if (!StringUtilsExt.equals(curPK[pkColumnIdx].getName(), targetPK[pkColumnIdx].getName(), _caseSensitive)) | |
{ | |
changePK = true; | |
} | |
} | |
} | |
if (changePK) | |
{ | |
if (_log.isInfoEnabled()) | |
{ | |
_log.info("The primary key of table " + intermediateTable.getName() + " needs to be changed"); | |
} | |
if (_generatePrimaryKeyChanges) | |
{ | |
PrimaryKeyChange change = new PrimaryKeyChange(intermediateTable.getName(), | |
getIntermediateColumnNamesFor(targetPK, intermediateTable)); | |
changes.add(change); | |
change.apply(intermediateModel, changePK); | |
} | |
else | |
{ | |
RemovePrimaryKeyChange removePKChange = new RemovePrimaryKeyChange(intermediateTable.getName()); | |
AddPrimaryKeyChange addPKChange = new AddPrimaryKeyChange(intermediateTable.getName(), | |
getIntermediateColumnNamesFor(targetPK, intermediateTable)); | |
changes.add(removePKChange); | |
changes.add(addPKChange); | |
removePKChange.apply(intermediateModel, _caseSensitive); | |
addPKChange.apply(intermediateModel, _caseSensitive); | |
} | |
} | |
} | |
return changes; | |
} | |
/** | |
* Compares the two columns and returns the change necessary to create the second | |
* column from the first one if they differe. | |
* | |
* @param sourceTable The source table which contains the source column | |
* @param sourceColumn The source column | |
* @param targetTable The target table which contains the target column | |
* @param targetColumn The target column | |
* @return The change or <code>null</code> if the columns are the same | |
*/ | |
protected ColumnDefinitionChange compareColumns(Table sourceTable, | |
Column sourceColumn, | |
Table targetTable, | |
Column targetColumn) | |
{ | |
if (ColumnDefinitionChange.isChanged(getPlatformInfo(), sourceColumn, targetColumn)) | |
{ | |
Column newColumnDef = _cloneHelper.clone(sourceColumn, true); | |
int targetTypeCode = _platformInfo.getTargetJdbcType(targetColumn.getTypeCode()); | |
boolean sizeMatters = _platformInfo.hasSize(targetTypeCode); | |
boolean scaleMatters = _platformInfo.hasPrecisionAndScale(targetTypeCode); | |
newColumnDef.setTypeCode(targetColumn.getTypeCode()); | |
newColumnDef.setSize(sizeMatters || scaleMatters ? targetColumn.getSize() : null); | |
newColumnDef.setAutoIncrement(targetColumn.isAutoIncrement()); | |
newColumnDef.setRequired(targetColumn.isRequired()); | |
newColumnDef.setDescription(targetColumn.getDescription()); | |
newColumnDef.setDefaultValue(targetColumn.getDefaultValue()); | |
return new ColumnDefinitionChange(sourceTable.getName(), sourceColumn.getName(), newColumnDef); | |
} | |
else | |
{ | |
return null; | |
} | |
} | |
/** | |
* Searches in the given table for a corresponding foreign key. If the given key | |
* has no name, then a foreign key to the same table with the same columns (but not | |
* necessarily in the same order) is searched. If the given key has a name, then the | |
* corresponding key also needs to have the same name, or no name at all, but not a | |
* different one. | |
* | |
* @param table The table to search in | |
* @param fk The original foreign key | |
* @return The corresponding foreign key if found | |
*/ | |
protected ForeignKey findCorrespondingForeignKey(Table table, ForeignKey fk) | |
{ | |
for (int fkIdx = 0; fkIdx < table.getForeignKeyCount(); fkIdx++) | |
{ | |
ForeignKey curFk = table.getForeignKey(fkIdx); | |
if ((_caseSensitive && fk.equals(curFk)) || | |
(!_caseSensitive && fk.equalsIgnoreCase(curFk))) | |
{ | |
return curFk; | |
} | |
} | |
return null; | |
} | |
/** | |
* Searches in the given table for a corresponding index. If the given index | |
* has no name, then a index to the same table with the same columns in the | |
* same order is searched. If the given index has a name, then the a corresponding | |
* index also needs to have the same name, or no name at all, but not a different one. | |
* | |
* @param table The table to search in | |
* @param index The original index | |
* @return The corresponding index if found | |
*/ | |
protected Index findCorrespondingIndex(Table table, Index index) | |
{ | |
for (int indexIdx = 0; indexIdx < table.getIndexCount(); indexIdx++) | |
{ | |
Index curIndex = table.getIndex(indexIdx); | |
if ((_caseSensitive && index.equals(curIndex)) || | |
(!_caseSensitive && index.equalsIgnoreCase(curIndex))) | |
{ | |
return curIndex; | |
} | |
} | |
return null; | |
} | |
} |