All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kualigan.tools.liquibase.DiffResult Maven / Gradle / Ivy

There is a newer version: 2.0.2
Show newest version
// Copyright 2011 Leo Przybylski. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//    1. Redistributions of source code must retain the above copyright notice, this list of
//       conditions and the following disclaimer.
//
//    2. Redistributions in binary form must reproduce the above copyright notice, this list
//       of conditions and the following disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY  ''AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL  OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those of the
// authors and should not be interpreted as representing official policies, either expressed
// or implied, of Leo Przybylski.
package org.kualigan.tools.liquibase;

import liquibase.change.Change;
import liquibase.change.ColumnConfig;
import liquibase.change.ConstraintsConfig;
import liquibase.change.core.*;
import liquibase.changelog.ChangeSet;
import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.database.structure.*;
import liquibase.database.typeconversion.TypeConverter;
import liquibase.database.typeconversion.TypeConverterFactory;
import liquibase.diff.*;
import liquibase.exception.DatabaseException;
import liquibase.executor.ExecutorService;
import liquibase.logging.LogFactory;
import liquibase.parser.core.xml.LiquibaseEntityResolver;
import liquibase.parser.core.xml.XMLChangeLogSAXParser;
import liquibase.serializer.ChangeLogSerializer;
import liquibase.serializer.ChangeLogSerializerFactory;
import liquibase.serializer.core.xml.XMLChangeLogSerializer;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.core.RawSqlStatement;
import liquibase.statement.ext.DescribeSequenceStatement;
import liquibase.util.ISODateFormat;
import liquibase.util.StringUtils;
import liquibase.util.csv.CSVWriter;
import liquibase.util.xml.DefaultXmlWriter;
import liquibase.util.xml.XmlWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.*;

public class DiffResult {

	private String idRoot = String.valueOf(new Date().getTime());
	private int changeNumber = 1;

	private DatabaseSnapshot referenceSnapshot;
	private DatabaseSnapshot targetSnapshot;

	private DiffComparison productName;
	private DiffComparison productVersion;

	private SortedSet missingTables = new TreeSet
(); private SortedSet
unexpectedTables = new TreeSet
(); private SortedSet missingViews = new TreeSet(); private SortedSet unexpectedViews = new TreeSet(); private SortedSet changedViews = new TreeSet(); private SortedSet missingColumns = new TreeSet(); private SortedSet unexpectedColumns = new TreeSet(); private SortedSet changedColumns = new TreeSet(); private SortedSet missingForeignKeys = new TreeSet(); private SortedSet unexpectedForeignKeys = new TreeSet(); private SortedSet missingIndexes = new TreeSet(); private SortedSet unexpectedIndexes = new TreeSet(); private SortedSet missingPrimaryKeys = new TreeSet(); private SortedSet unexpectedPrimaryKeys = new TreeSet(); private SortedSet missingUniqueConstraints = new TreeSet(); private SortedSet unexpectedUniqueConstraints = new TreeSet(); private SortedSet missingSequences = new TreeSet(); private SortedSet unexpectedSequences = new TreeSet(); private boolean diffData = false; private String dataDir = null; private String changeSetContext; private String changeSetAuthor; private ChangeLogSerializerFactory serializerFactory = ChangeLogSerializerFactory.getInstance(); public DiffResult(DatabaseSnapshot referenceDatabaseSnapshot, DatabaseSnapshot targetDatabaseSnapshot) { this.referenceSnapshot = referenceDatabaseSnapshot; if (targetDatabaseSnapshot == null) { targetDatabaseSnapshot = new DatabaseSnapshot( referenceDatabaseSnapshot.getDatabase(), null); } this.targetSnapshot = targetDatabaseSnapshot; } public DiffComparison getProductName() { return productName; } public void setProductName(DiffComparison productName) { this.productName = productName; } public DiffComparison getProductVersion() { return productVersion; } public void setProductVersion(DiffComparison product) { this.productVersion = product; } public void addMissingTable(Table table) { missingTables.add(table); } public SortedSet
getMissingTables() { return missingTables; } public void addUnexpectedTable(Table table) { unexpectedTables.add(table); } public SortedSet
getUnexpectedTables() { return unexpectedTables; } public void addMissingView(View viewName) { missingViews.add(viewName); } public SortedSet getMissingViews() { return missingViews; } public void addUnexpectedView(View viewName) { unexpectedViews.add(viewName); } public SortedSet getUnexpectedViews() { return unexpectedViews; } public void addChangedView(View viewName) { changedViews.add(viewName); } public SortedSet getChangedViews() { return changedViews; } public void addMissingColumn(Column columnName) { missingColumns.add(columnName); } public SortedSet getMissingColumns() { return missingColumns; } public void addUnexpectedColumn(Column columnName) { unexpectedColumns.add(columnName); } public SortedSet getUnexpectedColumns() { return unexpectedColumns; } public void addChangedColumn(Column columnName) { changedColumns.add(columnName); } public SortedSet getChangedColumns() { return changedColumns; } public void addMissingForeignKey(ForeignKey fkName) { missingForeignKeys.add(fkName); } public SortedSet getMissingForeignKeys() { return missingForeignKeys; } public void addUnexpectedForeignKey(ForeignKey fkName) { unexpectedForeignKeys.add(fkName); } public SortedSet getUnexpectedForeignKeys() { return unexpectedForeignKeys; } public void addMissingIndex(Index fkName) { missingIndexes.add(fkName); } public SortedSet getMissingIndexes() { return missingIndexes; } public void addUnexpectedIndex(Index fkName) { unexpectedIndexes.add(fkName); } public SortedSet getUnexpectedIndexes() { return unexpectedIndexes; } public void addMissingPrimaryKey(PrimaryKey primaryKey) { missingPrimaryKeys.add(primaryKey); } public SortedSet getMissingPrimaryKeys() { return missingPrimaryKeys; } public void addUnexpectedPrimaryKey(PrimaryKey primaryKey) { unexpectedPrimaryKeys.add(primaryKey); } public SortedSet getUnexpectedPrimaryKeys() { return unexpectedPrimaryKeys; } public void addMissingSequence(Sequence sequence) { missingSequences.add(sequence); } public SortedSet getMissingSequences() { return missingSequences; } public void addUnexpectedSequence(Sequence sequence) { unexpectedSequences.add(sequence); } public SortedSet getUnexpectedSequences() { return unexpectedSequences; } public void addMissingUniqueConstraint(UniqueConstraint uniqueConstraint) { missingUniqueConstraints.add(uniqueConstraint); } public SortedSet getMissingUniqueConstraints() { return this.missingUniqueConstraints; } public void addUnexpectedUniqueConstraint(UniqueConstraint uniqueConstraint) { unexpectedUniqueConstraints.add(uniqueConstraint); } public SortedSet getUnexpectedUniqueConstraints() { return unexpectedUniqueConstraints; } public boolean shouldDiffData() { return diffData; } public void setDiffData(boolean diffData) { this.diffData = diffData; } public String getDataDir() { return dataDir; } public void setDataDir(String dataDir) { this.dataDir = dataDir; } public String getChangeSetContext() { return changeSetContext; } public void setChangeSetContext(String changeSetContext) { this.changeSetContext = changeSetContext; } public boolean differencesFound() throws DatabaseException,IOException{ boolean differencesInData=false; if(shouldDiffData()) { List changeSets = new ArrayList(); addInsertDataChanges(changeSets, dataDir); differencesInData=!changeSets.isEmpty(); } return getMissingColumns().size()>0 || getMissingForeignKeys().size()>0 || getMissingIndexes().size()>0 || getMissingPrimaryKeys().size()>0 || getMissingSequences().size()>0 || getMissingTables().size()>0 || getMissingUniqueConstraints().size()>0 || getMissingViews().size()>0 || getUnexpectedColumns().size()>0 || getUnexpectedForeignKeys().size()>0 || getUnexpectedIndexes().size()>0 || getUnexpectedPrimaryKeys().size()>0 || getUnexpectedSequences().size()>0 || getUnexpectedTables().size()>0 || getUnexpectedUniqueConstraints().size()>0 || getUnexpectedViews().size()>0 || differencesInData; } public void printResult(PrintStream out) throws DatabaseException { out.println("Reference Database: " + referenceSnapshot.getDatabase()); out.println("Target Database: " + targetSnapshot.getDatabase()); printComparision("Product Name", productName, out); printComparision("Product Version", productVersion, out); printSetComparison("Missing Tables", getMissingTables(), out); printSetComparison("Unexpected Tables", getUnexpectedTables(), out); printSetComparison("Missing Views", getMissingViews(), out); printSetComparison("Unexpected Views", getUnexpectedViews(), out); printSetComparison("Changed Views", getChangedViews(), out); printSetComparison("Missing Columns", getMissingColumns(), out); printSetComparison("Unexpected Columns", getUnexpectedColumns(), out); printColumnComparison(getChangedColumns(), out); printSetComparison("Missing Foreign Keys", getMissingForeignKeys(), out); printSetComparison("Unexpected Foreign Keys", getUnexpectedForeignKeys(), out); printSetComparison("Missing Primary Keys", getMissingPrimaryKeys(), out); printSetComparison("Unexpected Primary Keys", getUnexpectedPrimaryKeys(), out); printSetComparison("Unexpected Unique Constraints", getUnexpectedUniqueConstraints(), out); printSetComparison("Missing Unique Constraints", getMissingUniqueConstraints(), out); printSetComparison("Missing Indexes", getMissingIndexes(), out); printSetComparison("Unexpected Indexes", getUnexpectedIndexes(), out); printSetComparison("Missing Sequences", getMissingSequences(), out); printSetComparison("Unexpected Sequences", getUnexpectedSequences(), out); } private void printSetComparison(String title, SortedSet objects, PrintStream out) { out.print(title + ": "); if (objects.size() == 0) { out.println("NONE"); } else { out.println(); for (Object object : objects) { out.println(" " + object); } } } private void printColumnComparison(SortedSet changedColumns, PrintStream out) { out.print("Changed Columns: "); if (changedColumns.size() == 0) { out.println("NONE"); } else { out.println(); for (Column column : changedColumns) { out.println(" " + column); Column baseColumn = referenceSnapshot.getColumn(column .getTable().getName(), column.getName()); if (baseColumn != null) { if (baseColumn.isDataTypeDifferent(column)) { out.println(" from " + TypeConverterFactory.getInstance().findTypeConverter(referenceSnapshot.getDatabase()).convertToDatabaseTypeString(baseColumn, referenceSnapshot.getDatabase()) + " to " + TypeConverterFactory.getInstance().findTypeConverter(targetSnapshot.getDatabase()).convertToDatabaseTypeString(targetSnapshot.getColumn(column.getTable().getName(), column.getName()), targetSnapshot.getDatabase())); } if (baseColumn.isNullabilityDifferent(column)) { Boolean nowNullable = targetSnapshot.getColumn( column.getTable().getName(), column.getName()) .isNullable(); if (nowNullable == null) { nowNullable = Boolean.TRUE; } if (nowNullable) { out.println(" now nullable"); } else { out.println(" now not null"); } } } } } } private void printComparision(String title, DiffComparison comparison, PrintStream out) { out.print(title + ":"); if (comparison == null) { out.print("NULL"); return; } if (comparison.areTheSame()) { out.println(" EQUAL"); } else { out.println(); out.println(" Reference: '" + comparison.getReferenceVersion() + "'"); out.println(" Target: '" + comparison.getTargetVersion() + "'"); } } public void printChangeLog(String changeLogFile, Database targetDatabase) throws ParserConfigurationException, IOException, DatabaseException { ChangeLogSerializer changeLogSerializer = serializerFactory.getSerializer(changeLogFile); this.printChangeLog(changeLogFile, targetDatabase, changeLogSerializer); } public void printChangeLog(PrintStream out, Database targetDatabase) throws ParserConfigurationException, IOException, DatabaseException { this.printChangeLog(out, targetDatabase, new XMLChangeLogSerializer()); } public void printChangeLog(String changeLogFile, Database targetDatabase, ChangeLogSerializer changeLogSerializer) throws ParserConfigurationException, IOException, DatabaseException { File file = new File(changeLogFile); if (!file.exists()) { LogFactory.getLogger().info(file + " does not exist, creating"); FileOutputStream stream = new FileOutputStream(file); printChangeLog(new PrintStream(stream), targetDatabase, changeLogSerializer); stream.close(); } else { LogFactory.getLogger().info(file + " exists, appending"); ByteArrayOutputStream out = new ByteArrayOutputStream(); printChangeLog(new PrintStream(out), targetDatabase, changeLogSerializer); String xml = new String(out.toByteArray()); xml = xml.replaceFirst("(?ms).*]*>", ""); xml = xml.replaceFirst("", ""); xml = xml.trim(); if ("".equals( xml )) { LogFactory.getLogger().info("No changes found, nothing to do"); return; } String lineSeparator = System.getProperty("line.separator"); BufferedReader fileReader = new BufferedReader(new FileReader(file)); String line; long offset = 0; while ((line = fileReader.readLine()) != null) { int index = line.indexOf(""); if (index >= 0) { offset += index; } else { offset += line.getBytes().length; offset += lineSeparator.getBytes().length; } } fileReader.close(); fileReader = new BufferedReader(new FileReader(file)); fileReader.skip(offset); fileReader.close(); // System.out.println("resulting XML: " + xml.trim()); RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); randomAccessFile.seek(offset); randomAccessFile.writeBytes(" "); randomAccessFile.write( xml.getBytes() ); randomAccessFile.writeBytes(lineSeparator); randomAccessFile.writeBytes("" + lineSeparator); randomAccessFile.close(); // BufferedWriter fileWriter = new BufferedWriter(new // FileWriter(file)); // fileWriter.append(xml); // fileWriter.close(); } } /** * Prints changeLog that would bring the target database to be the same as * the reference database */ public void printChangeLog(PrintStream out, Database targetDatabase, ChangeLogSerializer changeLogSerializer) throws ParserConfigurationException, IOException, DatabaseException { List changeSets = new ArrayList(); addMissingTableChanges(changeSets, targetDatabase); addMissingColumnChanges(changeSets, targetDatabase); addChangedColumnChanges(changeSets); addMissingPrimaryKeyChanges(changeSets); addUnexpectedPrimaryKeyChanges(changeSets); addUnexpectedForeignKeyChanges(changeSets); addMissingUniqueConstraintChanges(changeSets); addMissingIndexChanges(changeSets); addUnexpectedUniqueConstraintChanges(changeSets); if (diffData) { addInsertDataChanges(changeSets, dataDir); } addMissingForeignKeyChanges(changeSets); addUnexpectedIndexChanges(changeSets); addUnexpectedColumnChanges(changeSets); addMissingSequenceChanges(changeSets); addUnexpectedSequenceChanges(changeSets); addMissingViewChanges(changeSets); addUnexpectedViewChanges(changeSets); addChangedViewChanges(changeSets); addUnexpectedTableChanges(changeSets); changeLogSerializer.write(changeSets, out); out.flush(); } private ChangeSet generateChangeSet(Change change) { ChangeSet changeSet = generateChangeSet(); changeSet.addChange(change); return changeSet; } private ChangeSet generateChangeSet() { return new ChangeSet(generateId(), getChangeSetAuthor(), false, false, null, getChangeSetContext(), null); } private String getChangeSetAuthor() { if (changeSetAuthor != null) { return changeSetAuthor; } String author = System.getProperty("user.name"); if (StringUtils.trimToNull(author) == null) { return "diff-generated"; } else { return author + " (generated)"; } } public void setChangeSetAuthor(String changeSetAuthor) { this.changeSetAuthor = changeSetAuthor; } public void setIdRoot(String idRoot) { this.idRoot = idRoot; } protected String generateId() { return idRoot + "-" + changeNumber++; } private void addUnexpectedIndexChanges(List changes) { for (Index index : getUnexpectedIndexes()) { if (index.getAssociatedWith().contains(Index.MARK_PRIMARY_KEY) || index.getAssociatedWith().contains(Index.MARK_FOREIGN_KEY) || index.getAssociatedWith().contains(Index.MARK_UNIQUE_CONSTRAINT)) { continue; } DropIndexChange change = new DropIndexChange(); change.setTableName(index.getTable().getName()); change.setSchemaName(index.getTable().getSchema()); change.setIndexName(index.getName()); change.setAssociatedWith(index.getAssociatedWithAsString()); changes.add(generateChangeSet(change)); } } private void addMissingIndexChanges(List changes) { for (Index index : getMissingIndexes()) { CreateIndexChange change = new CreateIndexChange(); change.setTableName(index.getTable().getName()); change.setTablespace(index.getTablespace()); change.setSchemaName(index.getTable().getSchema()); change.setIndexName(index.getName()); change.setUnique(index.isUnique()); change.setAssociatedWith(index.getAssociatedWithAsString()); if (index.getAssociatedWith().contains(Index.MARK_PRIMARY_KEY) || index.getAssociatedWith().contains(Index.MARK_FOREIGN_KEY) || index.getAssociatedWith().contains(Index.MARK_UNIQUE_CONSTRAINT)) { continue; } for (String columnName : index.getColumns()) { ColumnConfig column = new ColumnConfig(); column.setName(columnName); change.addColumn(column); } changes.add(generateChangeSet(change)); } } private void addUnexpectedPrimaryKeyChanges(List changes) { for (PrimaryKey pk : getUnexpectedPrimaryKeys()) { if (!getUnexpectedTables().contains(pk.getTable())) { DropPrimaryKeyChange change = new DropPrimaryKeyChange(); change.setTableName(pk.getTable().getName()); change.setSchemaName(pk.getTable().getSchema()); change.setConstraintName(pk.getName()); changes.add(generateChangeSet(change)); } } } private void addMissingPrimaryKeyChanges(List changes) { for (PrimaryKey pk : getMissingPrimaryKeys()) { AddPrimaryKeyChange change = new AddPrimaryKeyChange(); change.setTableName(pk.getTable().getName()); change.setSchemaName(pk.getTable().getSchema()); change.setConstraintName(pk.getName()); change.setColumnNames(pk.getColumnNames()); change.setTablespace(pk.getTablespace()); changes.add(generateChangeSet(change)); } } private void addUnexpectedUniqueConstraintChanges(List changes) { for (UniqueConstraint uc : getUnexpectedUniqueConstraints()) { // Need check for nulls here due to NullPointerException using Postgres if (null != uc) { if (null != uc.getTable()) { DropUniqueConstraintChange change = new DropUniqueConstraintChange(); change.setTableName(uc.getTable().getName()); change.setSchemaName(uc.getTable().getSchema()); change.setConstraintName(uc.getName()); changes.add(generateChangeSet(change)); } } } } private void addMissingUniqueConstraintChanges(List changes) { for (UniqueConstraint uc : getMissingUniqueConstraints()) { // Need check for nulls here due to NullPointerException using Postgres if (null != uc) if (null != uc.getTable()) { AddUniqueConstraintChange change = new AddUniqueConstraintChange(); change.setTableName(uc.getTable().getName()); change.setTablespace(uc.getTablespace()); change.setSchemaName(uc.getTable().getSchema()); change.setConstraintName(uc.getName()); change.setColumnNames(uc.getColumnNames()); change.setDeferrable(uc.isDeferrable()); change.setInitiallyDeferred(uc.isInitiallyDeferred()); change.setDisabled(uc.isDisabled()); changes.add(generateChangeSet(change)); } } } private void addUnexpectedForeignKeyChanges(List changes) { for (ForeignKey fk : getUnexpectedForeignKeys()) { DropForeignKeyConstraintChange change = new DropForeignKeyConstraintChange(); change.setConstraintName(fk.getName()); change.setBaseTableName(fk.getForeignKeyTable().getName()); change.setBaseTableSchemaName(fk.getForeignKeyTable().getSchema()); changes.add(generateChangeSet(change)); } } private void addMissingForeignKeyChanges(List changes) { for (ForeignKey fk : getMissingForeignKeys()) { AddForeignKeyConstraintChange change = new AddForeignKeyConstraintChange(); change.setConstraintName(fk.getName()); change.setReferencedTableName(fk.getPrimaryKeyTable().getName()); change.setReferencedTableSchemaName(fk.getPrimaryKeyTable() .getSchema()); change.setReferencedColumnNames(fk.getPrimaryKeyColumns()); change.setBaseTableName(fk.getForeignKeyTable().getName()); change.setBaseTableSchemaName(fk.getForeignKeyTable().getSchema()); change.setBaseColumnNames(fk.getForeignKeyColumns()); change.setDeferrable(fk.isDeferrable()); change.setInitiallyDeferred(fk.isInitiallyDeferred()); change.setOnUpdate(fk.getUpdateRule()); change.setOnDelete(fk.getDeleteRule()); change.setReferencesUniqueColumn(fk.getReferencesUniqueColumn()); changes.add(generateChangeSet(change)); } } private void addUnexpectedSequenceChanges(List changes) { for (Sequence sequence : getUnexpectedSequences()) { DropSequenceChange change = new DropSequenceChange(); change.setSequenceName(sequence.getName()); change.setSchemaName(sequence.getSchema()); changes.add(generateChangeSet(change)); } } private void addMissingSequenceChanges(List changes) { for (Sequence sequence : getMissingSequences()) { CreateSequenceChange change = new CreateSequenceChange(); change.setSequenceName(sequence.getName()); change.setSchemaName(sequence.getSchema()); try { final DescribeSequenceStatement statement = new DescribeSequenceStatement(sequence.getName()); final BigInteger startValue = (BigInteger) ExecutorService.getInstance().getExecutor(referenceSnapshot.getDatabase()).queryForObject(statement, BigInteger.class); change.setStartValue(startValue); } catch (Exception e) { e.printStackTrace(); // quietly don't set start value } changes.add(generateChangeSet(change)); } } private void addUnexpectedColumnChanges(List changes) { for (Column column : getUnexpectedColumns()) { if (!shouldModifyColumn(column)) { continue; } DropColumnChange change = new DropColumnChange(); change.setTableName(column.getTable().getName()); change.setSchemaName(column.getTable().getSchema()); change.setColumnName(column.getName()); changes.add(generateChangeSet(change)); } } private void addMissingViewChanges(List changes) { for (View view : getMissingViews()) { CreateViewChange change = new CreateViewChange(); change.setViewName(view.getName()); change.setSchemaName(view.getSchema()); String selectQuery = view.getDefinition(); if (selectQuery == null) { selectQuery = "COULD NOT DETERMINE VIEW QUERY"; } change.setSelectQuery(selectQuery); changes.add(generateChangeSet(change)); } } private void addChangedViewChanges(List changes) { for (View view : getChangedViews()) { CreateViewChange change = new CreateViewChange(); change.setViewName(view.getName()); change.setSchemaName(view.getSchema()); String selectQuery = view.getDefinition(); if (selectQuery == null) { selectQuery = "COULD NOT DETERMINE VIEW QUERY"; } change.setSelectQuery(selectQuery); change.setReplaceIfExists(true); changes.add(generateChangeSet(change)); } } private void addChangedColumnChanges(List changes) { for (Column column : getChangedColumns()) { if (!shouldModifyColumn(column)) { continue; } TypeConverter targetTypeConverter = TypeConverterFactory.getInstance().findTypeConverter(targetSnapshot.getDatabase()); boolean foundDifference = false; Column referenceColumn = referenceSnapshot.getColumn(column.getTable().getName(), column.getName()); if (column.isDataTypeDifferent(referenceColumn)) { ModifyDataTypeChange change = new ModifyDataTypeChange(); change.setTableName(column.getTable().getName()); change.setSchemaName(column.getTable().getSchema()); change.setColumnName(column.getName()); change.setNewDataType(targetTypeConverter.convertToDatabaseTypeString(referenceColumn, targetSnapshot.getDatabase())); changes.add(generateChangeSet(change)); foundDifference = true; } if (column.isNullabilityDifferent(referenceColumn)) { if (referenceColumn.isNullable() == null || referenceColumn.isNullable()) { DropNotNullConstraintChange change = new DropNotNullConstraintChange(); change.setTableName(column.getTable().getName()); change.setSchemaName(column.getTable().getSchema()); change.setColumnName(column.getName()); change.setColumnDataType(targetTypeConverter.convertToDatabaseTypeString(referenceColumn, targetSnapshot.getDatabase())); changes.add(generateChangeSet(change)); foundDifference = true; } else { AddNotNullConstraintChange change = new AddNotNullConstraintChange(); change.setTableName(column.getTable().getName()); change.setSchemaName(column.getTable().getSchema()); change.setColumnName(column.getName()); change.setColumnDataType(targetTypeConverter.convertToDatabaseTypeString(referenceColumn, targetSnapshot.getDatabase())); Object defaultValue = column.getDefaultValue(); String defaultValueString; if (defaultValue != null) { defaultValueString = targetTypeConverter.getDataType(defaultValue).convertObjectToString(defaultValue, targetSnapshot.getDatabase()); if (defaultValueString != null) { change.setDefaultNullValue(defaultValueString); } } changes.add(generateChangeSet(change)); foundDifference = true; } } if (!foundDifference) { throw new RuntimeException("Unknown difference"); } } } private boolean shouldModifyColumn(Column column) { return column.getView() == null && !referenceSnapshot.getDatabase().isLiquibaseTable( column.getTable().getName()); } private void addUnexpectedViewChanges(List changes) { for (View view : getUnexpectedViews()) { DropViewChange change = new DropViewChange(); change.setViewName(view.getName()); change.setSchemaName(view.getSchema()); changes.add(generateChangeSet(change)); } } private void addMissingColumnChanges(List changes, Database database) { for (Column column : getMissingColumns()) { if (!shouldModifyColumn(column)) { continue; } AddColumnChange change = new AddColumnChange(); change.setTableName(column.getTable().getName()); change.setSchemaName(column.getTable().getSchema()); ColumnConfig columnConfig = new ColumnConfig(); columnConfig.setName(column.getName()); String dataType = TypeConverterFactory.getInstance().findTypeConverter(database).convertToDatabaseTypeString(column, database); columnConfig.setType(dataType); Object defaultValue = column.getDefaultValue(); if (defaultValue != null) { String defaultValueString = TypeConverterFactory.getInstance() .findTypeConverter(database).getDataType(defaultValue) .convertObjectToString(defaultValue, database); if (defaultValueString != null) { defaultValueString = defaultValueString.replaceFirst("'", "").replaceAll("'$", ""); } columnConfig.setDefaultValue(defaultValueString); } if (column.getRemarks() != null) { columnConfig.setRemarks(column.getRemarks()); } ConstraintsConfig constraintsConfig = columnConfig.getConstraints(); if (column.isNullable() != null && !column.isNullable()) { if (constraintsConfig == null) { constraintsConfig = new ConstraintsConfig(); } constraintsConfig.setNullable(false); } if (column.isUnique()) { if (constraintsConfig == null) { constraintsConfig = new ConstraintsConfig(); } constraintsConfig.setUnique(true); } if (constraintsConfig != null) { columnConfig.setConstraints(constraintsConfig); } change.addColumn(columnConfig); changes.add(generateChangeSet(change)); } } private void addMissingTableChanges(List changes, Database database) { for (Table missingTable : getMissingTables()) { if (referenceSnapshot.getDatabase().isLiquibaseTable( missingTable.getName())) { continue; } CreateTableChange change = new CreateTableChange(); change.setTableName(missingTable.getName()); change.setSchemaName(missingTable.getSchema()); if (missingTable.getRemarks() != null) { change.setRemarks(missingTable.getRemarks()); } for (Column column : missingTable.getColumns()) { ColumnConfig columnConfig = new ColumnConfig(); columnConfig.setName(column.getName()); columnConfig.setType(TypeConverterFactory.getInstance().findTypeConverter(database).convertToDatabaseTypeString(column, database)); ConstraintsConfig constraintsConfig = null; if (column.isPrimaryKey()) { PrimaryKey primaryKey = null; for (PrimaryKey pk : getMissingPrimaryKeys()) { if (pk.getTable().getName().equalsIgnoreCase(missingTable.getName())) { primaryKey = pk; } } if (primaryKey == null || primaryKey.getColumnNamesAsList().size() == 1) { constraintsConfig = new ConstraintsConfig(); constraintsConfig.setPrimaryKey(true); constraintsConfig.setPrimaryKeyTablespace(column.getTablespace()); if (primaryKey != null) { constraintsConfig.setPrimaryKeyName(primaryKey.getName()); getMissingPrimaryKeys().remove(primaryKey); } } } if (column.isAutoIncrement()) { columnConfig.setAutoIncrement(true); } if (column.isNullable() != null && !column.isNullable()) { if (constraintsConfig == null) { constraintsConfig = new ConstraintsConfig(); } constraintsConfig.setNullable(false); } if (column.isUnique()) { if (constraintsConfig == null) { constraintsConfig = new ConstraintsConfig(); } constraintsConfig.setUnique(true); } if (constraintsConfig != null) { columnConfig.setConstraints(constraintsConfig); } Object defaultValue = column.getDefaultValue(); if (defaultValue == null) { // do nothing } else if (column.isAutoIncrement()) { // do nothing } else if (defaultValue instanceof Date) { columnConfig.setDefaultValueDate((Date) defaultValue); } else if (defaultValue instanceof Boolean) { columnConfig.setDefaultValueBoolean(((Boolean) defaultValue)); } else if (defaultValue instanceof Number) { columnConfig.setDefaultValueNumeric(((Number) defaultValue)); } else if (defaultValue instanceof DatabaseFunction) { columnConfig.setDefaultValueComputed((DatabaseFunction) defaultValue); } else { columnConfig.setDefaultValue(defaultValue.toString()); } if (column.getRemarks() != null) { columnConfig.setRemarks(column.getRemarks()); } change.addColumn(columnConfig); } changes.add(generateChangeSet(change)); } } private void addUnexpectedTableChanges(List changes) { for (Table unexpectedTable : getUnexpectedTables()) { DropTableChange change = new DropTableChange(); change.setTableName(unexpectedTable.getName()); change.setSchemaName(unexpectedTable.getSchema()); changes.add(generateChangeSet(change)); } } private void addInsertDataChanges(List changeSets, String dataDir) throws DatabaseException, IOException { try { String schema = referenceSnapshot.getSchema(); for (Table table : referenceSnapshot.getTables()) { List changes = new ArrayList(); List rs = ExecutorService.getInstance().getExecutor(referenceSnapshot.getDatabase()).queryForList(new RawSqlStatement("SELECT * FROM "+ referenceSnapshot.getDatabase().escapeTableName(schema,table.getName()))); if (rs.size() == 0) { continue; } List columnNames = new ArrayList(); for (Column column : table.getColumns()) { columnNames.add(column.getName()); } // if dataDir is not null, print out a csv file and use loadData // tag if (dataDir != null) { String fileName = table.getName().toLowerCase() + ".csv"; if (dataDir != null) { fileName = dataDir + "/" + fileName; } File parentDir = new File(dataDir); if (!parentDir.exists()) { parentDir.mkdirs(); } if (!parentDir.isDirectory()) { throw new RuntimeException(parentDir + " is not a directory"); } CSVWriter outputFile = new CSVWriter(new FileWriter( fileName)); String[] dataTypes = new String[columnNames.size()]; String[] line = new String[columnNames.size()]; for (int i = 0; i < columnNames.size(); i++) { line[i] = columnNames.get(i); } outputFile.writeNext(line); for (Map row : rs) { line = new String[columnNames.size()]; for (int i = 0; i < columnNames.size(); i++) { Object value = row.get(columnNames.get(i).toUpperCase()); if (dataTypes[i] == null && value != null) { if (value instanceof Number) { dataTypes[i] = "NUMERIC"; } else if (value instanceof Boolean) { dataTypes[i] = "BOOLEAN"; } else if (value instanceof Date) { dataTypes[i] = "DATE"; } else { dataTypes[i] = "STRING"; } } if (value == null) { line[i] = "NULL"; } else { if (value instanceof Date) { line[i] = new ISODateFormat().format(((Date) value)); } else { line[i] = value.toString(); } } } outputFile.writeNext(line); } outputFile.flush(); outputFile.close(); LoadDataChange change = new LoadDataChange(); change.setFile(fileName); change.setEncoding("UTF-8"); change.setSchemaName(schema); change.setTableName(table.getName()); for (int i = 0; i < columnNames.size(); i++) { String colName = columnNames.get(i); LoadDataColumnConfig columnConfig = new LoadDataColumnConfig(); columnConfig.setHeader(colName); columnConfig.setName(colName); columnConfig.setType(dataTypes[i]); change.addColumn(columnConfig); } changes.add(change); } else { // if dataDir is null, build and use insert tags for (Map row : rs) { InsertDataChange change = new InsertDataChange(); change.setSchemaName(schema); change.setTableName(table.getName()); // loop over all columns for this row for (int i = 0; i < columnNames.size(); i++) { ColumnConfig column = new ColumnConfig(); column.setName(columnNames.get(i)); Object value = row.get(columnNames.get(i).toUpperCase()); if (value == null) { column.setValue(null); } else if (value instanceof Number) { column.setValueNumeric((Number) value); } else if (value instanceof Boolean) { column.setValueBoolean((Boolean) value); } else if (value instanceof Date) { column.setValueDate((Date) value); } else { // string column.setValue(value.toString().replace("\\","\\\\")); } change.addColumn(column); } // for each row, add a new change // (there will be one group per table) changes.add(change); } } if (changes.size() > 0) { ChangeSet changeSet = generateChangeSet(); for (Change change : changes) { changeSet.addChange(change); } changeSets.add(changeSet); } } } catch (Exception e) { throw new RuntimeException(e); } } }