@Override
  public Change[] fixChanged(
      DatabaseObject changedObject,
      ObjectDifferences differences,
      DiffOutputControl control,
      Database referenceDatabase,
      final Database comparisonDatabase,
      ChangeGeneratorChain chain) {
    Table table = (Table) changedObject;

    Difference changedRemarks = differences.getDifference("remarks");
    if (changedRemarks != null) {
      SetTableRemarksChange change = new SetTableRemarksChange();
      if (control.getIncludeCatalog()) {
        change.setCatalogName(table.getSchema().getCatalogName());
      }
      if (control.getIncludeSchema()) {
        change.setSchemaName(table.getSchema().getName());
      }

      change.setTableName(table.getName());
      change.setRemarks(table.getRemarks());

      return new Change[] {change};
    }

    return null;
  }
  protected Table readTable(CachedRow tableMetadataResultSet, Database database)
      throws SQLException, DatabaseException {
    String rawTableName = tableMetadataResultSet.getString("TABLE_NAME");
    String rawSchemaName = StringUtils.trimToNull(tableMetadataResultSet.getString("TABLE_SCHEM"));
    String rawCatalogName = StringUtils.trimToNull(tableMetadataResultSet.getString("TABLE_CAT"));
    String remarks = StringUtils.trimToNull(tableMetadataResultSet.getString("REMARKS"));
    if (remarks != null) {
      remarks = remarks.replace("''", "'"); // come back escaped sometimes
    }

    Table table = new Table().setName(cleanNameFromDatabase(rawTableName, database));
    table.setRemarks(remarks);

    CatalogAndSchema schemaFromJdbcInfo =
        ((AbstractJdbcDatabase) database).getSchemaFromJdbcInfo(rawCatalogName, rawSchemaName);
    table.setSchema(
        new Schema(schemaFromJdbcInfo.getCatalogName(), schemaFromJdbcInfo.getSchemaName()));

    return table;
  }
  public static List<SqlStatement> getAlterTableStatements(
      AlterTableVisitor alterTableVisitor,
      Database database,
      String catalogName,
      String schemaName,
      String tableName)
      throws DatabaseException {

    DatabaseSnapshot snapshot = null; // todo
    List<SqlStatement> statements = new ArrayList<SqlStatement>();

    Table table = null;
    try {
      table =
          SnapshotGeneratorFactory.getInstance()
              .createSnapshot(
                  (Table)
                      new Table().setName(tableName).setSchema(new Schema(new Catalog(null), null)),
                  database);
    } catch (InvalidExampleException e) {
      throw new UnexpectedLiquibaseException(e);
    }

    List<ColumnConfig> createColumns = new Vector<ColumnConfig>();
    List<ColumnConfig> copyColumns = new Vector<ColumnConfig>();
    if (table != null) {
      for (Column column : table.getColumns()) {
        ColumnConfig new_column = new ColumnConfig(column);
        if (alterTableVisitor.createThisColumn(new_column)) {
          createColumns.add(new_column);
        }
        ColumnConfig copy_column = new ColumnConfig(column);
        if (alterTableVisitor.copyThisColumn(copy_column)) {
          copyColumns.add(copy_column);
        }
      }
    }
    for (ColumnConfig column : alterTableVisitor.getColumnsToAdd()) {
      if (alterTableVisitor.createThisColumn(column)) {
        createColumns.add(column);
      }
      if (alterTableVisitor.copyThisColumn(column)) {
        copyColumns.add(column);
      }
    }

    List<Index> newIndices = new Vector<Index>();
    for (Index index :
        new ArrayList<
            Index>()) { // todo SnapshotGeneratorFactory.getInstance().getGenerator(Index.class,
                        // database).get(new Schema(new Catalog(null), schemaName), database)) {
      if (index.getTable().getName().equalsIgnoreCase(tableName)) {
        if (alterTableVisitor.createThisIndex(index)) {
          newIndices.add(index);
        }
      }
    }

    // rename table
    String temp_table_name = tableName + "_temporary";
    statements.add(new RenameTableStatement(catalogName, schemaName, tableName, temp_table_name));
    // create temporary table
    CreateTableChange ct_change_tmp = new CreateTableChange();
    ct_change_tmp.setSchemaName(schemaName);
    ct_change_tmp.setTableName(tableName);
    for (ColumnConfig column : createColumns) {
      ct_change_tmp.addColumn(column);
    }
    statements.addAll(Arrays.asList(ct_change_tmp.generateStatements(database)));
    // copy rows to temporary table
    statements.add(new CopyRowsStatement(temp_table_name, tableName, copyColumns));
    // delete original table
    statements.add(new DropTableStatement(catalogName, schemaName, temp_table_name, false));
    // validate indices
    statements.add(new ReindexStatement(catalogName, schemaName, tableName));
    // add remaining indices
    for (Index index_config : newIndices) {
      statements.add(
          new CreateIndexStatement(
              index_config.getName(),
              catalogName,
              schemaName,
              tableName,
              index_config.isUnique(),
              index_config.getAssociatedWithAsString(),
              index_config.getColumns().toArray(new String[index_config.getColumns().size()])));
    }

    return statements;
  }
  @Override
  public void checkDatabaseChangeLogTable(
      final boolean updateExistingNullChecksums,
      final DatabaseChangeLog databaseChangeLog,
      final Contexts contexts)
      throws DatabaseException {
    if (updateExistingNullChecksums && databaseChangeLog == null) {
      throw new DatabaseException("changeLog parameter is required if updating existing checksums");
    }

    Executor executor = ExecutorService.getInstance().getExecutor(this);

    Table changeLogTable =
        SnapshotGeneratorFactory.getInstance()
            .getDatabaseChangeLogTable(new SnapshotControl(this, Table.class, Column.class), this);

    List<SqlStatement> statementsToExecute = new ArrayList<SqlStatement>();

    boolean changeLogCreateAttempted = false;
    if (changeLogTable != null) {
      boolean hasDescription = changeLogTable.getColumn("DESCRIPTION") != null;
      boolean hasComments = changeLogTable.getColumn("COMMENTS") != null;
      boolean hasTag = changeLogTable.getColumn("TAG") != null;
      boolean hasLiquibase = changeLogTable.getColumn("LIQUIBASE") != null;
      boolean liquibaseColumnNotRightSize = false;
      if (!getConnection().getDatabaseProductName().equals("SQLite")) {
        liquibaseColumnNotRightSize =
            changeLogTable.getColumn("LIQUIBASE").getType().getColumnSize() != 20;
      }
      boolean hasOrderExecuted = changeLogTable.getColumn("ORDEREXECUTED") != null;
      boolean checksumNotRightSize = false;
      boolean hasExecTypeColumn = changeLogTable.getColumn("EXECTYPE") != null;

      if (!hasDescription) {
        executor.comment("Adding missing databasechangelog.description column");
        statementsToExecute.add(
            new AddColumnStatement(
                getLiquibaseCatalogName(),
                getLiquibaseSchemaName(),
                getDatabaseChangeLogTableName(),
                "DESCRIPTION",
                "VARCHAR(255)",
                null));
      }
      if (!hasTag) {
        executor.comment("Adding missing databasechangelog.tag column");
        statementsToExecute.add(
            new AddColumnStatement(
                getLiquibaseCatalogName(),
                getLiquibaseSchemaName(),
                getDatabaseChangeLogTableName(),
                "TAG",
                "VARCHAR(255)",
                null));
      }
      if (!hasComments) {
        executor.comment("Adding missing databasechangelog.comments column");
        statementsToExecute.add(
            new AddColumnStatement(
                getLiquibaseCatalogName(),
                getLiquibaseSchemaName(),
                getDatabaseChangeLogTableName(),
                "COMMENTS",
                "VARCHAR(255)",
                null));
      }
      if (!hasLiquibase) {
        executor.comment("Adding missing databasechangelog.liquibase column");
        statementsToExecute.add(
            new AddColumnStatement(
                getLiquibaseCatalogName(),
                getLiquibaseSchemaName(),
                getDatabaseChangeLogTableName(),
                "LIQUIBASE",
                "VARCHAR(255)",
                null));
      }
      if (!hasOrderExecuted) {
        executor.comment("Adding missing databasechangelog.orderexecuted column");
        statementsToExecute.add(
            new AddColumnStatement(
                getLiquibaseCatalogName(),
                getLiquibaseSchemaName(),
                getDatabaseChangeLogTableName(),
                "ORDEREXECUTED",
                "INT",
                null));
        statementsToExecute.add(
            new UpdateStatement(
                    getLiquibaseCatalogName(),
                    getLiquibaseSchemaName(),
                    getDatabaseChangeLogTableName())
                .addNewColumnValue("ORDEREXECUTED", -1));
        statementsToExecute.add(
            new SetNullableStatement(
                getLiquibaseCatalogName(),
                getLiquibaseSchemaName(),
                getDatabaseChangeLogTableName(),
                "ORDEREXECUTED",
                "INT",
                false));
      }
      if (checksumNotRightSize) {
        executor.comment("Modifying size of databasechangelog.md5sum column");

        statementsToExecute.add(
            new ModifyDataTypeStatement(
                getLiquibaseCatalogName(),
                getLiquibaseSchemaName(),
                getDatabaseChangeLogTableName(),
                "MD5SUM",
                "VARCHAR(35)"));
      }
      if (liquibaseColumnNotRightSize) {
        executor.comment("Modifying size of databasechangelog.liquibase column");

        statementsToExecute.add(
            new ModifyDataTypeStatement(
                getLiquibaseCatalogName(),
                getLiquibaseSchemaName(),
                getDatabaseChangeLogTableName(),
                "LIQUIBASE",
                "VARCHAR(20)"));
      }
      if (!hasExecTypeColumn) {
        executor.comment("Adding missing databasechangelog.exectype column");
        statementsToExecute.add(
            new AddColumnStatement(
                getLiquibaseCatalogName(),
                getLiquibaseSchemaName(),
                getDatabaseChangeLogTableName(),
                "EXECTYPE",
                "VARCHAR(10)",
                null));
        statementsToExecute.add(
            new UpdateStatement(
                    getLiquibaseCatalogName(),
                    getLiquibaseSchemaName(),
                    getDatabaseChangeLogTableName())
                .addNewColumnValue("EXECTYPE", "EXECUTED"));
        statementsToExecute.add(
            new SetNullableStatement(
                getLiquibaseCatalogName(),
                getLiquibaseSchemaName(),
                getDatabaseChangeLogTableName(),
                "EXECTYPE",
                "VARCHAR(10)",
                false));
      }

      List<Map> md5sumRS =
          ExecutorService.getInstance()
              .getExecutor(this)
              .queryForList(
                  new SelectFromDatabaseChangeLogStatement(
                      new SelectFromDatabaseChangeLogStatement.ByNotNullCheckSum(), "MD5SUM"));
      if (md5sumRS.size() > 0) {
        String md5sum = md5sumRS.get(0).get("MD5SUM").toString();
        if (!md5sum.startsWith(CheckSum.getCurrentVersion() + ":")) {
          executor.comment(
              "DatabaseChangeLog checksums are an incompatible version.  Setting them to null so they will be updated on next database update");
          statementsToExecute.add(
              new RawSqlStatement(
                  "UPDATE "
                      + escapeTableName(
                          getLiquibaseCatalogName(),
                          getLiquibaseSchemaName(),
                          getDatabaseChangeLogTableName())
                      + " SET MD5SUM=null"));
        }
      }

    } else if (!changeLogCreateAttempted) {
      executor.comment("Create Database Change Log Table");
      SqlStatement createTableStatement = new CreateDatabaseChangeLogTableStatement();
      if (!canCreateChangeLogTable()) {
        throw new DatabaseException(
            "Cannot create "
                + escapeTableName(
                    getLiquibaseCatalogName(),
                    getLiquibaseSchemaName(),
                    getDatabaseChangeLogTableName())
                + " table for your database.\n\n"
                + "Please construct it manually using the following SQL as a base and re-run Liquibase:\n\n"
                + createTableStatement);
      }
      // If there is no table in the database for recording change history create one.
      statementsToExecute.add(createTableStatement);
      LogFactory.getLogger()
          .info(
              "Creating database history table with name: "
                  + escapeTableName(
                      getLiquibaseCatalogName(),
                      getLiquibaseSchemaName(),
                      getDatabaseChangeLogTableName()));
      //                }
    }

    for (SqlStatement sql : statementsToExecute) {
      executor.execute(sql);
      this.commit();
    }

    if (updateExistingNullChecksums) {
      for (RanChangeSet ranChangeSet : this.getRanChangeSetList()) {
        if (ranChangeSet.getLastCheckSum() == null) {
          ChangeSet changeSet = databaseChangeLog.getChangeSet(ranChangeSet);
          if (changeSet != null
              && new ContextChangeSetFilter(contexts).accepts(changeSet)
              && new DbmsChangeSetFilter(this).accepts(changeSet)) {
            LogFactory.getLogger()
                .info(
                    "Updating null or out of date checksum on changeSet "
                        + changeSet
                        + " to correct value");
            executor.execute(new UpdateChangeSetChecksumStatement(changeSet));
          }
        }
      }
      commit();
      resetRanChangeSetList();
    }
  }