Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/ModelComparator.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/ModelComparator.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/ModelComparator.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/ModelComparator.java Mon Dec 10 00:20:47 2007 @@ -23,15 +23,16 @@ import java.util.HashMap; import java.util.List; -import org.apache.commons.lang.StringUtils; 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.StringUtils; /** * Compares two database models and creates change objects that express how to @@ -39,8 +40,6 @@ * are changed in the process, however, it is also assumed that the models do not * change in between. * - * TODO: Add support and tests for the change of the column order - * * @version $Revision: $ */ public class ModelComparator @@ -50,19 +49,79 @@ /** 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 caseSensitive Whether comparison is case sensitive + * @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 true. + * + * @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 false and the + * comparator will create additional primary key changes. + * The default value is true. + * + * @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 */ - public ModelComparator(PlatformInfo platformInfo, boolean caseSensitive) + protected PlatformInfo getPlatformInfo() { - _platformInfo = platformInfo; - _caseSensitive = caseSensitive; + return _platformInfo; + } + + /** + * Determines whether comparison should be case sensitive. + * + * @return true if case matters + */ + protected boolean isCaseSensitive() + { + return _caseSensitive; } /** @@ -75,217 +134,661 @@ */ 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(); - for (int tableIdx = 0; tableIdx < targetModel.getTableCount(); tableIdx++) + changes.addAll(checkForRemovedForeignKeys(sourceModel, intermediateModel, targetModel)); + changes.addAll(checkForRemovedTables(sourceModel, intermediateModel, targetModel)); + + for (int tableIdx = 0; tableIdx < intermediateModel.getTableCount(); tableIdx++) { - Table targetTable = targetModel.getTable(tableIdx); - Table sourceTable = sourceModel.findTable(targetTable.getName(), _caseSensitive); + 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); + } - if (sourceTable == null) + 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++) { - if (_log.isInfoEnabled()) - { - _log.info("Table " + targetTable.getName() + " needs to be added"); - } - changes.add(new AddTableChange(targetTable)); - for (int fkIdx = 0; fkIdx < targetTable.getForeignKeyCount(); fkIdx++) + ForeignKey sourceFk = intermediateFks[fkIdx]; + ForeignKey targetFk = targetTable == null ? null : findCorrespondingForeignKey(targetTable, sourceFk); + + if (targetFk == null) { - // we have to use target table's definition here because the - // complete table is new - changes.add(new AddForeignKeyChange(targetTable, targetTable.getForeignKey(fkIdx))); + 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); } } - else + } + 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++) { - changes.addAll(compareTables(sourceModel, sourceTable, targetModel, targetTable)); + 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 < sourceModel.getTableCount(); tableIdx++) + for (int tableIdx = 0; tableIdx < intermediateTables.length; tableIdx++) { - Table sourceTable = sourceModel.getTable(tableIdx); - Table targetTable = targetModel.findTable(sourceTable.getName(), _caseSensitive); + Table intermediateTable = intermediateTables[tableIdx]; + Table targetTable = targetModel.findTable(intermediateTable.getName(), _caseSensitive); - if ((targetTable == null) && (sourceTable.getName() != null) && (sourceTable.getName().length() > 0)) + if (targetTable == null) { if (_log.isInfoEnabled()) { - _log.info("Table " + sourceTable.getName() + " needs to be removed"); - } - changes.add(new RemoveTableChange(sourceTable)); - // we assume that the target model is sound, ie. that there are no longer any foreign - // keys to this table in the target model; thus we already have removeFK changes for - // these from the compareTables method and we only need to create changes for the fks - // originating from this table - for (int fkIdx = 0; fkIdx < sourceTable.getForeignKeyCount(); fkIdx++) - { - changes.add(new RemoveForeignKeyChange(sourceTable, sourceTable.getForeignKey(fkIdx))); + _log.info("Table " + intermediateTable.getName() + " needs to be removed"); } + + RemoveTableChange tableChange = new RemoveTableChange(intermediateTable.getName()); + + 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 which contains the source table - * @param sourceTable The source table - * @param targetModel The target model which contains the target table - * @param targetTable The target table + * 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 */ - public List compareTables(Database sourceModel, - Table sourceTable, - Database targetModel, - Table targetTable) + protected List checkForAddedTables(Database sourceModel, + Database intermediateModel, + Database targetModel) { - ArrayList changes = new ArrayList(); + List changes = new ArrayList(); - for (int fkIdx = 0; fkIdx < sourceTable.getForeignKeyCount(); fkIdx++) + for (int tableIdx = 0; tableIdx < targetModel.getTableCount(); tableIdx++) { - ForeignKey sourceFk = sourceTable.getForeignKey(fkIdx); - ForeignKey targetFk = findCorrespondingForeignKey(targetTable, sourceFk); + Table targetTable = targetModel.getTable(tableIdx); + Table intermediateTable = intermediateModel.findTable(targetTable.getName(), _caseSensitive); - if (targetFk == null) + if (intermediateTable == null) { if (_log.isInfoEnabled()) { - _log.info("Foreign key " + sourceFk + " needs to be removed from table " + sourceTable.getName()); + _log.info("Table " + targetTable.getName() + " needs to be added"); } - changes.add(new RemoveForeignKeyChange(sourceTable, sourceFk)); + + // 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; + } - for (int fkIdx = 0; fkIdx < targetTable.getForeignKeyCount(); fkIdx++) - { - ForeignKey targetFk = targetTable.getForeignKey(fkIdx); - ForeignKey sourceFk = findCorrespondingForeignKey(sourceTable, targetFk); + /** + * 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); - if (sourceFk == null) + 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)); + + if (!tableDefinitionChanges.isEmpty()) + { + if ((_tableDefCangePredicate == null) || _tableDefCangePredicate.areSupported(tmpTable, tableDefinitionChanges)) { - if (_log.isInfoEnabled()) + 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++) { - _log.info("Foreign key " + targetFk + " needs to be created for table " + sourceTable.getName()); + 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); + } + } + } } - // we have to use the target table here because the foreign key might - // reference a new column - changes.add(new AddForeignKeyChange(targetTable, targetFk)); + + 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 indexIdx = 0; indexIdx < sourceTable.getIndexCount(); indexIdx++) + for (int idx = 0; idx < columns.length; idx++) { - Index sourceIndex = sourceTable.getIndex(indexIdx); + 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 " + sourceTable.getName()); + _log.info("Index " + sourceIndex.getName() + " needs to be removed from table " + intermediateTable.getName()); } - changes.add(new RemoveIndexChange(sourceTable, sourceIndex)); + + 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 sourceIndex = findCorrespondingIndex(sourceTable, targetIndex); + Index sourceIndex = findCorrespondingIndex(intermediateTable, targetIndex); if (sourceIndex == null) { if (_log.isInfoEnabled()) { - _log.info("Index " + targetIndex.getName() + " needs to be created for table " + sourceTable.getName()); + _log.info("Index " + targetIndex.getName() + " needs to be created for table " + intermediateTable.getName()); } - // we have to use the target table here because the index might - // reference a new column - changes.add(new AddIndexChange(targetTable, targetIndex)); + + Index clonedIndex = _cloneHelper.clone(targetIndex, intermediateTable, _caseSensitive); + AddIndexChange change = new AddIndexChange(intermediateTable.getName(), clonedIndex); + + changes.add(change); + change.apply(intermediateModel, _caseSensitive); } } + return changes; + } - HashMap addColumnChanges = new HashMap(); + /** + * 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 = sourceTable.findColumn(targetColumn.getName(), _caseSensitive); + Column sourceColumn = intermediateTable.findColumn(targetColumn.getName(), _caseSensitive); - if (sourceColumn == null) + if (sourceColumn != null) { - if (_log.isInfoEnabled()) + 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()) { - _log.info("Column " + targetColumn.getName() + " needs to be created for table " + sourceTable.getName()); + numChangedPKs++; } + } + } - AddColumnChange change = new AddColumnChange(sourceTable, - targetColumn, - columnIdx > 0 ? targetTable.getColumn(columnIdx - 1) : null, - columnIdx < targetTable.getColumnCount() - 1 ? targetTable.getColumn(columnIdx + 1) : null); + if (!newPositions.isEmpty()) + { + ColumnOrderChange change = new ColumnOrderChange(intermediateTable.getName(), newPositions); - changes.add(change); - addColumnChanges.put(targetColumn, change); + 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))); } - else + 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) { - changes.addAll(compareColumns(sourceTable, sourceColumn, targetTable, targetColumn)); + 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); } } - // if the last columns in the target table are added, then we note this at the changes - for (int columnIdx = targetTable.getColumnCount() - 1; columnIdx >= 0; columnIdx--) + 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); - AddColumnChange change = (AddColumnChange)addColumnChanges.get(targetColumn); + Column targetColumn = targetTable.getColumn(columnIdx); + Column sourceColumn = intermediateTable.findColumn(targetColumn.getName(), _caseSensitive); - if (change == null) + if (sourceColumn == null) { - // column was not added, so we can ignore any columns before it that were added - break; + 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); } - else + } + 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) { - change.setAtEnd(true); + 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 ((sourcePK.length == 0) && (targetPK.length > 0)) + if ((curPK.length == 0) && (targetPK.length > 0)) { if (_log.isInfoEnabled()) { - _log.info("A primary key needs to be added to the table " + sourceTable.getName()); + _log.info("A primary key needs to be added to the table " + intermediateTable.getName()); } - // we have to use the target table here because the primary key might - // reference a new column - changes.add(new AddPrimaryKeyChange(targetTable, targetPK)); + + AddPrimaryKeyChange change = new AddPrimaryKeyChange(intermediateTable.getName(), getIntermediateColumnNamesFor(targetPK, intermediateTable)); + + changes.add(change); + change.apply(intermediateModel, _caseSensitive); } - else if ((targetPK.length == 0) && (sourcePK.length > 0)) + else if ((targetPK.length == 0) && (curPK.length > 0)) { if (_log.isInfoEnabled()) { - _log.info("The primary key needs to be removed from the table " + sourceTable.getName()); + _log.info("The primary key needs to be removed from the table " + intermediateTable.getName()); } - changes.add(new RemovePrimaryKeyChange(sourceTable, sourcePK)); + + RemovePrimaryKeyChange change = new RemovePrimaryKeyChange(intermediateTable.getName()); + + changes.add(change); + change.apply(intermediateModel, _caseSensitive); } - else if ((sourcePK.length > 0) && (targetPK.length > 0)) + else { boolean changePK = false; - if (sourcePK.length != targetPK.length) + if ((curPK.length != targetPK.length) || (!_canDropPrimaryKeyColumns && sourcePK.length > targetPK.length)) { changePK = true; } - else + else if ((curPK.length > 0) && (targetPK.length > 0)) { - for (int pkColumnIdx = 0; (pkColumnIdx < sourcePK.length) && !changePK; pkColumnIdx++) + for (int pkColumnIdx = 0; (pkColumnIdx < curPK.length) && !changePK; pkColumnIdx++) { - if ((_caseSensitive && !sourcePK[pkColumnIdx].getName().equals(targetPK[pkColumnIdx].getName())) || - (!_caseSensitive && !sourcePK[pkColumnIdx].getName().equalsIgnoreCase(targetPK[pkColumnIdx].getName()))) + if (!StringUtils.equals(curPK[pkColumnIdx].getName(), targetPK[pkColumnIdx].getName(), _caseSensitive)) { changePK = true; } @@ -295,101 +798,66 @@ { if (_log.isInfoEnabled()) { - _log.info("The primary key of table " + sourceTable.getName() + " needs to be changed"); - } - changes.add(new PrimaryKeyChange(sourceTable, targetPK)); - } - } - - HashMap columnPosChanges = new HashMap(); - - for (int columnIdx = 0; columnIdx < sourceTable.getColumnCount(); columnIdx++) - { - Column sourceColumn = sourceTable.getColumn(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 " + sourceTable.getName()); + _log.info("The primary key of table " + intermediateTable.getName() + " needs to be changed"); } - changes.add(new RemoveColumnChange(sourceTable, sourceColumn)); - } - else - { - int targetColumnIdx = targetTable.getColumnIndex(targetColumn); - - if (targetColumnIdx != columnIdx) + if (_generatePrimaryKeyChanges) { - columnPosChanges.put(sourceColumn, new Integer(targetColumnIdx)); + 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); } } } - if (!columnPosChanges.isEmpty()) - { - changes.add(new ColumnOrderChange(sourceTable, columnPosChanges)); - } - return changes; } /** - * Compares the two columns and returns the changes necessary to create the second - * column from the first one. + * 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 changes + * @return The change or null if the columns are the same */ - public List compareColumns(Table sourceTable, - Column sourceColumn, - Table targetTable, - Column targetColumn) + protected ColumnDefinitionChange compareColumns(Table sourceTable, + Column sourceColumn, + Table targetTable, + Column targetColumn) { - ArrayList changes = new ArrayList(); - - if (_platformInfo.getTargetJdbcType(targetColumn.getTypeCode()) != sourceColumn.getTypeCode()) - { - changes.add(new ColumnDataTypeChange(sourceTable, sourceColumn, targetColumn.getTypeCode())); - } - - boolean sizeMatters = _platformInfo.hasSize(sourceColumn.getTypeCode()); - boolean scaleMatters = _platformInfo.hasPrecisionAndScale(sourceColumn.getTypeCode()); - - if (sizeMatters && - !StringUtils.equals(sourceColumn.getSize(), targetColumn.getSize())) - { - changes.add(new ColumnSizeChange(sourceTable, sourceColumn, targetColumn.getSizeAsInt(), targetColumn.getScale())); - } - else if (scaleMatters && - (!StringUtils.equals(sourceColumn.getSize(), targetColumn.getSize()) || - (sourceColumn.getScale() != targetColumn.getScale()))) + if (ColumnDefinitionChange.isChanged(getPlatformInfo(), sourceColumn, targetColumn)) { - changes.add(new ColumnSizeChange(sourceTable, sourceColumn, targetColumn.getSizeAsInt(), targetColumn.getScale())); + 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); } - - Object sourceDefaultValue = sourceColumn.getParsedDefaultValue(); - Object targetDefaultValue = targetColumn.getParsedDefaultValue(); - - if (((sourceDefaultValue == null) && (targetDefaultValue != null)) || - ((sourceDefaultValue != null) && !sourceDefaultValue.equals(targetDefaultValue))) - { - changes.add(new ColumnDefaultValueChange(sourceTable, sourceColumn, targetColumn.getDefaultValue())); - } - - if (sourceColumn.isRequired() != targetColumn.isRequired()) + else { - changes.add(new ColumnRequiredChange(sourceTable, sourceColumn)); + return null; } - if (sourceColumn.isAutoIncrement() != targetColumn.isAutoIncrement()) - { - changes.add(new ColumnAutoIncrementChange(sourceTable, sourceColumn)); - } - - return changes; } /** @@ -403,7 +871,7 @@ * @param fk The original foreign key * @return The corresponding foreign key if found */ - private ForeignKey findCorrespondingForeignKey(Table table, ForeignKey fk) + protected ForeignKey findCorrespondingForeignKey(Table table, ForeignKey fk) { for (int fkIdx = 0; fkIdx < table.getForeignKeyCount(); fkIdx++) { @@ -428,7 +896,7 @@ * @param index The original index * @return The corresponding index if found */ - private Index findCorrespondingIndex(Table table, Index index) + protected Index findCorrespondingIndex(Table table, Index index) { for (int indexIdx = 0; indexIdx < table.getIndexCount(); indexIdx++) { Added: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/Pair.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/Pair.java?rev=602807&view=auto ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/Pair.java (added) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/Pair.java Mon Dec 10 00:20:47 2007 @@ -0,0 +1,65 @@ +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. + */ + +/** + * Represents a pair of objects. + * + * @version $Revision: $ + */ +public class Pair +{ + /** The first object. */ + private final Object _firstObj; + /** The first object. */ + private final Object _secondObj; + + /** + * Creates a pair object. + * + * @param firstObj The first object + * @param secondObj The second object + */ + public Pair(Object firstObj, Object secondObj) + { + _firstObj = firstObj; + _secondObj = secondObj; + } + + /** + * Returns the first object of the pair. + * + * @return The first object + */ + public Object getFirst() + { + return _firstObj; + } + + /** + * Returns the second object of the pair. + * + * @return The second object + */ + public Object getSecond() + { + return _secondObj; + } +} Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/PrimaryKeyChange.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/PrimaryKeyChange.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/PrimaryKeyChange.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/PrimaryKeyChange.java Mon Dec 10 00:20:47 2007 @@ -30,40 +30,27 @@ */ public class PrimaryKeyChange extends TableChangeImplBase { - /** The columns making up the original primary key. */ - private Column[] _oldPrimaryKeyColumns; - /** The columns making up the new primary key. */ - private Column[] _newPrimaryKeyColumns; + /** The names of the columns making up the new primary key. */ + private String[] _newPrimaryKeyColumns; /** * Creates a new change object. * - * @param table The table whose primary key is to be changed - * @param newPrimaryKeyColumns The columns making up the new primary key + * @param tableName The name of the table whose primary key is to be changed + * @param newPrimaryKeyColumns The names of the columns making up the new primary key */ - public PrimaryKeyChange(Table table, Column[] newPrimaryKeyColumns) + public PrimaryKeyChange(String tableName, String[] newPrimaryKeyColumns) { - super(table); - _oldPrimaryKeyColumns = table.getPrimaryKeyColumns(); + super(tableName); _newPrimaryKeyColumns = newPrimaryKeyColumns; } /** - * Returns the columns making up the original primary key. + * Returns the names of the columns making up the new primary key. * - * @return The columns + * @return The column names */ - public Column[] getOldPrimaryKeyColumns() - { - return _oldPrimaryKeyColumns; - } - - /** - * Returns the columns making up the new primary key. - * - * @return The columns - */ - public Column[] getNewPrimaryKeyColumns() + public String[] getNewPrimaryKeyColumns() { return _newPrimaryKeyColumns; } @@ -73,17 +60,16 @@ */ public void apply(Database model, boolean caseSensitive) { - Table table = findChangedTable(model, caseSensitive); + Table table = findChangedTable(model, caseSensitive); + Column[] pkCols = table.getPrimaryKeyColumns(); - for (int idx = 0; idx < _oldPrimaryKeyColumns.length; idx++) + for (int idx = 0; idx < pkCols.length; idx++) { - Column column = table.findColumn(_oldPrimaryKeyColumns[idx].getName(), caseSensitive); - - column.setPrimaryKey(false); + pkCols[idx].setPrimaryKey(false); } for (int idx = 0; idx < _newPrimaryKeyColumns.length; idx++) { - Column column = table.findColumn(_newPrimaryKeyColumns[idx].getName(), caseSensitive); + Column column = table.findColumn(_newPrimaryKeyColumns[idx], caseSensitive); column.setPrimaryKey(true); } Added: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RecreateTableChange.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RecreateTableChange.java?rev=602807&view=auto ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RecreateTableChange.java (added) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RecreateTableChange.java Mon Dec 10 00:20:47 2007 @@ -0,0 +1,103 @@ +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.List; + +import org.apache.ddlutils.model.CloneHelper; +import org.apache.ddlutils.model.Database; +import org.apache.ddlutils.model.Table; + +/** + * Represents the recreation of a table, i.e. creating a temporary table using the target table definition, + * copying the data from the original table into this temporary table, dropping the original table and + * finally renaming the temporary table to the final table. This change is only created by the model + * comparator if the table definition change predicate flags a table change as unsupported. + * + * @version $Revision: $ + */ +public class RecreateTableChange extends TableChangeImplBase +{ + /** The target table definition. */ + private Table _targetTable; + /** The original table changes, one of which is unsupported by the current platform. */ + private List _originalChanges; + + /** + * Creates a new change object for recreating a table. This change is used to specify that a table needs + * to be dropped and then re-created (with changes). In the standard model comparison algorithm, it will + * replace all direct changes to the table's columns (i.e. foreign key and index changes are unaffected). + * + * @param tableName The name of the table + * @param targetTable The table as it should be; note that the change object will keep a reference + * to this table which means that it should not be changed after creating this + * change object + * @param originalChanges The original changes that this change object replaces + */ + public RecreateTableChange(String tableName, Table targetTable, List originalChanges) + { + super(tableName); + _targetTable = targetTable; + _originalChanges = originalChanges; + } + + /** + * Returns the original table changes, one of which is unsupported by the current platform. + * + * @return The table changes + */ + public List getOriginalChanges() + { + return _originalChanges; + } + + /** + * Returns the target table definition. Due to when an object of this kind is created in the comparison + * process, this table object will not have any foreign keys pointing to or from it, i.e. it is + * independent of any model. + * + * @return The table definition + */ + public Table getTargetTable() + { + return _targetTable; + } + + /** + * {@inheritDoc} + */ + public void apply(Database database, boolean caseSensitive) + { + // we only need to replace the table in the model, as there can't be a + // foreign key from or to it when these kind of changes are created + for (int tableIdx = 0; tableIdx < database.getTableCount(); tableIdx++) + { + Table curTable = database.getTable(tableIdx); + + if ((caseSensitive && curTable.getName().equals(getChangedTable())) || + (!caseSensitive && curTable.getName().equalsIgnoreCase(getChangedTable()))) + { + database.removeTable(tableIdx); + database.addTable(tableIdx, new CloneHelper().clone(_targetTable, true, false, database, caseSensitive)); + break; + } + } + } +} Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveColumnChange.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveColumnChange.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveColumnChange.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveColumnChange.java Mon Dec 10 00:20:47 2007 @@ -19,9 +19,7 @@ * under the License. */ -import org.apache.ddlutils.model.Column; import org.apache.ddlutils.model.Database; -import org.apache.ddlutils.model.Table; /** * Represents the removal of a column from a table. @@ -33,12 +31,12 @@ /** * Creates a new change object. * - * @param table The table to remove the column from - * @param column The column + * @param tableName The name of the table to remove the column from + * @param columnName The column's name */ - public RemoveColumnChange(Table table, Column column) + public RemoveColumnChange(String tableName, String columnName) { - super(table, column); + super(tableName, columnName); } /** Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveForeignKeyChange.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveForeignKeyChange.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveForeignKeyChange.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveForeignKeyChange.java Mon Dec 10 00:20:47 2007 @@ -21,7 +21,6 @@ import org.apache.ddlutils.model.Database; import org.apache.ddlutils.model.ForeignKey; -import org.apache.ddlutils.model.Table; /** * Represents the removal of a foreign key from a table. Note that for @@ -35,12 +34,12 @@ /** * Creates a new change object. * - * @param table The table to remove the foreign key from + * @param tableName The name of the table to remove the foreign key from * @param foreignKey The foreign key */ - public RemoveForeignKeyChange(Table table, ForeignKey foreignKey) + public RemoveForeignKeyChange(String tableName, ForeignKey foreignKey) { - super(table, foreignKey); + super(tableName, foreignKey); } /** Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveIndexChange.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveIndexChange.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveIndexChange.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveIndexChange.java Mon Dec 10 00:20:47 2007 @@ -21,7 +21,6 @@ import org.apache.ddlutils.model.Database; import org.apache.ddlutils.model.Index; -import org.apache.ddlutils.model.Table; /** * Represents the removal of an index from a table. @@ -33,12 +32,12 @@ /** * Creates a new change object. * - * @param table The table to remove the index from - * @param index The index + * @param tableName The name of the table to remove the index from + * @param index The index */ - public RemoveIndexChange(Table table, Index index) + public RemoveIndexChange(String tableName, Index index) { - super(table, index); + super(tableName, index); } /** Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemovePrimaryKeyChange.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemovePrimaryKeyChange.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemovePrimaryKeyChange.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemovePrimaryKeyChange.java Mon Dec 10 00:20:47 2007 @@ -30,29 +30,14 @@ */ public class RemovePrimaryKeyChange extends TableChangeImplBase { - /** The columns making up the primary key. */ - private Column[] _primaryKeyColumns; - /** * Creates a new change object. * - * @param table The table to remove the primary key from - * @param primaryKeyColumns The columns making up the primary key + * @param tableName The name of he table to remove the primary key from */ - public RemovePrimaryKeyChange(Table table, Column[] primaryKeyColumns) + public RemovePrimaryKeyChange(String tableName) { - super(table); - _primaryKeyColumns = primaryKeyColumns; - } - - /** - * Returns the primary key columns making up the primary key. - * - * @return The primary key columns - */ - public Column[] getPrimaryKeyColumns() - { - return _primaryKeyColumns; + super(tableName); } /** @@ -60,13 +45,12 @@ */ public void apply(Database model, boolean caseSensitive) { - Table table = findChangedTable(model, caseSensitive); + Table table = findChangedTable(model, caseSensitive); + Column[] pkCols = table.getPrimaryKeyColumns(); - for (int idx = 0; idx < _primaryKeyColumns.length; idx++) + for (int idx = 0; idx < pkCols.length; idx++) { - Column column = table.findColumn(_primaryKeyColumns[idx].getName(), caseSensitive); - - column.setPrimaryKey(false); + pkCols[idx].setPrimaryKey(false); } } } Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveTableChange.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveTableChange.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveTableChange.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/RemoveTableChange.java Mon Dec 10 00:20:47 2007 @@ -25,6 +25,7 @@ /** * Represents the removal of a table from a model. * + * TODO: this should be a model change * @version $Revision: $ */ public class RemoveTableChange extends TableChangeImplBase @@ -32,11 +33,11 @@ /** * Creates a new change object. * - * @param table The table + * @param tableName The name of the table to be removed */ - public RemoveTableChange(Table table) + public RemoveTableChange(String tableName) { - super(table); + super(tableName); } /** Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableChange.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableChange.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableChange.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableChange.java Mon Dec 10 00:20:47 2007 @@ -30,11 +30,11 @@ public interface TableChange extends ModelChange { /** - * Returns the affected table from the original model. + * Returns the name of the affected table from the original model. * - * @return The affected table + * @return The name of the affected table */ - public Table getChangedTable(); + public String getChangedTable(); /** * Finds the table object corresponding to the changed table in the given database model. Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableChangeImplBase.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableChangeImplBase.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableChangeImplBase.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableChangeImplBase.java Mon Dec 10 00:20:47 2007 @@ -29,25 +29,25 @@ */ public abstract class TableChangeImplBase implements TableChange { - /** The affected table. */ - private Table _table; + /** The name of the affected table. */ + private String _tableName; /** * Creates a new change object. * - * @param table The table + * @param tableName The table's name */ - public TableChangeImplBase(Table table) + public TableChangeImplBase(String tableName) { - _table = table; + _tableName = tableName; } /** * {@inheritDoc} */ - public Table getChangedTable() + public String getChangedTable() { - return _table; + return _tableName; } /** @@ -55,6 +55,6 @@ */ public Table findChangedTable(Database model, boolean caseSensitive) { - return model.findTable(_table.getName(), caseSensitive); + return model.findTable(_tableName, caseSensitive); } } Added: db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableDefinitionChangesPredicate.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableDefinitionChangesPredicate.java?rev=602807&view=auto ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableDefinitionChangesPredicate.java (added) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/alteration/TableDefinitionChangesPredicate.java Mon Dec 10 00:20:47 2007 @@ -0,0 +1,45 @@ +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.List; + +import org.apache.ddlutils.model.Table; + +/** + * Defines a predicate that allows platforms to state whether all of the given table definition + * changes (i.e. column changes and primary key changes) are supported by the platform or not. + * + * @version $Revision: $ + */ +public interface TableDefinitionChangesPredicate +{ + /** + * Evaluates the given list of table changes and determines whether they are supported. + * + * @param intermediateTable The current table object which has certain non-table-definition + * changes already applied (those that would come before the give + * list of changes in the result of + * {@link ModelComparator#compare(org.apache.ddlutils.model.Database, org.apache.ddlutils.model.Database)} + * @param changes The non-empty list of changes + * @return true if the current plaform supports them + */ + public boolean areSupported(Table intermediateTable, List changes); +} Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/model/CascadeActionEnum.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/model/CascadeActionEnum.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/model/CascadeActionEnum.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/model/CascadeActionEnum.java Mon Dec 10 00:20:47 2007 @@ -26,8 +26,8 @@ import org.apache.commons.lang.enums.ValuedEnum; /** - * Represents the different cascade actions for {@link ForeignKey#onDelete} and - * {@link ForeignKey#onUdate}. + * Represents the different cascade actions for the onDelete and + * onUpdate properties of {@link ForeignKey}. * * @version $Revision: $ */ Added: db/ddlutils/trunk/src/java/org/apache/ddlutils/model/CloneHelper.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/model/CloneHelper.java?rev=602807&view=auto ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/model/CloneHelper.java (added) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/model/CloneHelper.java Mon Dec 10 00:20:47 2007 @@ -0,0 +1,221 @@ +package org.apache.ddlutils.model; + +/* + * 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. + */ + +/** + * Helper class that provides cloning of model elements. + * + * @version $Revision: $ + */ +public class CloneHelper +{ + /** + * Returns a deep clone of the given model object, including all tables, foreign keys, indexes etc. + * + * @param source The source model + * @return The clone + */ + public Database clone(Database source) + { + Database result = new Database(); + + result.setName(source.getName()); + result.setIdMethod(source.getIdMethod()); + result.setVersion(source.getVersion()); + + for (int tableIdx = 0; tableIdx < source.getTableCount(); tableIdx++) + { + Table sourceTable = source.getTable(tableIdx); + + result.addTable(clone(sourceTable, true, false, result, true)); + } + for (int tableIdx = 0; tableIdx < source.getTableCount(); tableIdx++) + { + Table sourceTable = source.getTable(tableIdx); + Table clonedTable = result.getTable(tableIdx); + + for (int fkIdx = 0; fkIdx < sourceTable.getForeignKeyCount(); fkIdx++) + { + ForeignKey sourceFk = sourceTable.getForeignKey(fkIdx); + + clonedTable.addForeignKey(clone(sourceFk, clonedTable, result, true)); + } + } + return result; + } + + /** + * Returns a clone of the given table. + * + * @param source The source table + * @param cloneIndexes Whether to clone indexes; if false then the clone will + * not have any indexes + * @param cloneForeignKeys Whether to clone foreign kets; if false then the clone + * will not have any foreign keys + * @param targetModel The target model, can be null if + * cloneForeignKeys=false + * @param caseSensitive Whether comparison is case sensitive (for cloning foreign keys) + * @return The clone + */ + public Table clone(Table source, boolean cloneIndexes, boolean cloneForeignKeys, Database targetModel, boolean caseSensitive) + { + Table result = new Table(); + + result.setCatalog(source.getCatalog()); + result.setSchema(source.getSchema()); + result.setName(source.getName()); + result.setType(source.getType()); + + for (int colIdx = 0; colIdx < source.getColumnCount(); colIdx++) + { + result.addColumn(clone(source.getColumn(colIdx), true)); + } + if (cloneIndexes) + { + for (int indexIdx = 0; indexIdx < source.getIndexCount(); indexIdx++) + { + result.addIndex(clone(source.getIndex(indexIdx), result, true)); + } + } + if (cloneForeignKeys) + { + for (int fkIdx = 0; fkIdx < source.getForeignKeyCount(); fkIdx++) + { + result.addForeignKey(clone(source.getForeignKey(fkIdx), result, targetModel, caseSensitive)); + } + } + + return result; + } + + /** + * Returns a clone of the given source column. + * + * @param source The source column + * @param clonePrimaryKeyStatus Whether to clone the column's primary key status; if false + * then the clone will not be a primary key column + * @return The clone + */ + public Column clone(Column source, boolean clonePrimaryKeyStatus) + { + Column result = new Column(); + + result.setName(source.getName()); + result.setJavaName(source.getJavaName()); + result.setPrimaryKey(clonePrimaryKeyStatus ? source.isPrimaryKey() : false); + result.setRequired(source.isRequired()); + result.setAutoIncrement(source.isAutoIncrement()); + result.setTypeCode(source.getTypeCode()); + result.setSize(source.getSize()); + result.setDefaultValue(source.getDefaultValue()); + + return result; + } + + /** + * Returns a clone of the given source index. + * + * @param source The source index + * @param targetTable The table whose columns shall be used by the clone + * @param caseSensitive Whether comparison is case sensitive (for finding the columns + * in the target table) + * @return The clone + */ + public Index clone(Index source, Table targetTable, boolean caseSensitive) + { + Index result = (source.isUnique() ? (Index)new UniqueIndex() : (Index)new NonUniqueIndex()); + + result.setName(source.getName()); + for (int colIdx = 0; colIdx < source.getColumnCount(); colIdx++) + { + IndexColumn column = source.getColumn(colIdx); + + result.addColumn(clone(column, targetTable, caseSensitive)); + } + return result; + } + + /** + * Returns a clone of the given index column. + * + * @param source The source index column + * @param targetTable The table containing the column to be used by the clone + * @param caseSensitive Whether comparison is case sensitive (for finding the columns + * in the target table) + * @return The clone + */ + public IndexColumn clone(IndexColumn source, Table targetTable, boolean caseSensitive) + { + IndexColumn result = new IndexColumn(); + + result.setColumn(targetTable.findColumn(source.getName(), caseSensitive)); + result.setOrdinalPosition(source.getOrdinalPosition()); + result.setSize(source.getSize()); + return result; + } + + /** + * Returns a clone of the given source foreign key. + * + * @param source The source foreign key + * @param owningTable The table owning the source foreign key + * @param targetModel The target model containing the tables that the clone shall link + * @param caseSensitive Whether comparison is case sensitive (for finding the columns + * in the target model) + * @return The clone + */ + public ForeignKey clone(ForeignKey source, Table owningTable, Database targetModel, boolean caseSensitive) + { + ForeignKey result = new ForeignKey(); + Table foreignTable = targetModel.findTable(source.getForeignTableName(), caseSensitive); + + result.setName(source.getName()); + result.setForeignTable(foreignTable); + result.setAutoIndexPresent(source.isAutoIndexPresent()); + + for (int refIdx = 0; refIdx < source.getReferenceCount(); refIdx++) + { + Reference ref = source.getReference(refIdx); + + result.addReference(clone(ref, owningTable, foreignTable, caseSensitive)); + } + + return result; + } + + /** + * Returns a clone of the given source reference. + * + * @param source The source reference + * @param localTable The table containing the local column to be used by the reference + * @param foreignTable The table containing the foreign column to be used by the reference + * @param caseSensitive Whether comparison is case sensitive (for finding the columns + * in the tables) + * @return The clone + */ + public Reference clone(Reference source, Table localTable, Table foreignTable, boolean caseSensitive) + { + Reference result = new Reference(); + + result.setLocalColumn(localTable.findColumn(source.getLocalColumnName(), caseSensitive)); + result.setForeignColumn(foreignTable.findColumn(source.getForeignColumnName(), caseSensitive)); + return result; + } +} Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/model/Column.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/model/Column.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/model/Column.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/model/Column.java Mon Dec 10 00:20:47 2007 @@ -36,7 +36,7 @@ * * @version $Revision$ */ -public class Column implements Cloneable, Serializable +public class Column implements Serializable { /** Unique ID for serialization purposes. */ private static final long serialVersionUID = -6226348998874210093L; @@ -478,29 +478,6 @@ public void setDefaultValue(String defaultValue) { _defaultValue = defaultValue; - } - - /** - * {@inheritDoc} - */ - public Object clone() throws CloneNotSupportedException - { - Column result = (Column)super.clone(); - - result._name = _name; - result._javaName = _javaName; - result._primaryKey = _primaryKey; - result._required = _required; - result._autoIncrement = _autoIncrement; - result._typeCode = _typeCode; - result._type = _type; - result._size = _size; - result._defaultValue = _defaultValue; - result._scale = _scale; - result._size = _size; - result._sizeAsInt = _sizeAsInt; - - return result; } /** Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/model/Database.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/model/Database.java?rev=602807&r1=602806&r2=602807&view=diff ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/model/Database.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/model/Database.java Mon Dec 10 00:20:47 2007 @@ -40,7 +40,7 @@ * * @version $Revision$ */ -public class Database implements Serializable, Cloneable +public class Database implements Serializable { /** Unique ID for serialization purposes. */ private static final long serialVersionUID = -3160443396757573868L; @@ -64,22 +64,32 @@ */ public void mergeWith(Database otherDb) throws ModelException { - for (Iterator it = otherDb._tables.iterator(); it.hasNext();) + CloneHelper cloneHelper = new CloneHelper(); + + for (int tableIdx = 0; tableIdx < otherDb.getTableCount(); tableIdx++) { - Table table = (Table)it.next(); + Table table = otherDb.getTable(tableIdx); if (findTable(table.getName()) != null) { // TODO: It might make more sense to log a warning and overwrite the table (or merge them) ? throw new ModelException("Cannot merge the models because table "+table.getName()+" already defined in this model"); } - try + else { - addTable((Table)table.clone()); + addTable(cloneHelper.clone(table, true, false, this, true)); } - catch (CloneNotSupportedException ex) + } + for (int tableIdx = 0; tableIdx < otherDb.getTableCount(); tableIdx++) + { + Table otherTable = otherDb.getTable(tableIdx); + Table localTable = findTable(otherTable.getName()); + + for (int fkIdx = 0; fkIdx < otherTable.getForeignKeyCount(); fkIdx++) { - // won't happen + ForeignKey fk = otherTable.getForeignKey(fkIdx); + + localTable.addForeignKey(cloneHelper.clone(fk, localTable, this, false)); } } } @@ -292,7 +302,7 @@ } if (namesOfProcessedColumns.contains(column.getName())) { - throw new ModelException("There are multiple column with the name "+column.getName()+" in the table "+curTable.getName()); + throw new ModelException("There are multiple columns with the name "+column.getName()+" in the table "+curTable.getName()); } namesOfProcessedColumns.add(column.getName()); @@ -526,21 +536,6 @@ public DynaBean createDynaBeanFor(String tableName, boolean caseSensitive) throws SqlDynaException { return getDynaClassCache().createNewInstance(findTable(tableName, caseSensitive)); - } - - /** - * {@inheritDoc} - */ - public Object clone() throws CloneNotSupportedException - { - Database result = (Database)super.clone(); - - result._name = _name; - result._idMethod = _idMethod; - result._version = _version; - result._tables = (ArrayList)_tables.clone(); - - return result; } /**