package liquibase.sqlgenerator.core;
import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.datatype.DataTypeFactory;
import liquibase.exception.DatabaseException;
import liquibase.exception.ValidationErrors;
import liquibase.sql.Sql;
import liquibase.sql.UnparsedSql;
import liquibase.sqlgenerator.SqlGeneratorChain;
import liquibase.sqlgenerator.SqlGeneratorFactory;
import liquibase.statement.core.TagDatabaseStatement;
import liquibase.statement.core.UpdateStatement;
import liquibase.structure.core.Column;
import liquibase.structure.core.Table;
public class TagDatabaseGenerator extends AbstractSqlGenerator<TagDatabaseStatement> {
@Override
public ValidationErrors validate(TagDatabaseStatement tagDatabaseStatement, Database database, SqlGeneratorChain sqlGeneratorChain) {
ValidationErrors validationErrors = new ValidationErrors();
validationErrors.checkRequiredField("tag", tagDatabaseStatement.getTag());
return validationErrors;
}
@Override
public Sql[] generateSql(TagDatabaseStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) {
String tableNameEscaped = database.escapeTableName(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogTableName());
String orderColumnNameEscaped = database.escapeObjectName("ORDEREXECUTED", Column.class);
String dateColumnNameEscaped = database.escapeObjectName("DATEEXECUTED", Column.class);
String tagColumnNameEscaped = database.escapeObjectName("TAG", Column.class);
String tagEscaped = DataTypeFactory.getInstance().fromObject(statement.getTag(), database).objectToSql(statement.getTag(), database);
if (database instanceof MySQLDatabase) {
return new Sql[]{
new UnparsedSql(
"UPDATE " + tableNameEscaped + " AS C " +
"INNER JOIN (" +
"SELECT " + orderColumnNameEscaped + ", " + dateColumnNameEscaped + " " +
"FROM " + tableNameEscaped +
" order by " + dateColumnNameEscaped + " desc, " + orderColumnNameEscaped + " desc limit 1) AS D " +
"ON C." + orderColumnNameEscaped + " = D." + orderColumnNameEscaped + " " +
"SET C." + tagColumnNameEscaped + " = " + tagEscaped)
};
} else if (database instanceof PostgresDatabase) {
return new Sql[]{
new UnparsedSql(
"UPDATE " + tableNameEscaped + " t SET TAG=" + tagEscaped +
" FROM (SELECT " + dateColumnNameEscaped + ", " + orderColumnNameEscaped + " FROM " + tableNameEscaped + " ORDER BY " + dateColumnNameEscaped + " DESC, " + orderColumnNameEscaped + " DESC LIMIT 1) sub " +
"WHERE t." + dateColumnNameEscaped + "=sub." + dateColumnNameEscaped + " AND t." + orderColumnNameEscaped + "=sub." + orderColumnNameEscaped)
};
} else if (database instanceof InformixDatabase) {
String tempTableNameEscaped = database.escapeObjectName("max_order_temp", Table.class);
return new Sql[]{
new UnparsedSql(
"SELECT MAX(" + dateColumnNameEscaped + ") AS " + dateColumnNameEscaped +
", MAX(" + orderColumnNameEscaped + ") AS " + orderColumnNameEscaped + " " +
"FROM " + tableNameEscaped + " " +
"INTO TEMP " + tempTableNameEscaped + " WITH NO LOG"),
new UnparsedSql(
"UPDATE " + tableNameEscaped + " " +
"SET TAG = " + tagEscaped + " " +
"WHERE " + dateColumnNameEscaped + " = (" +
"SELECT " + dateColumnNameEscaped + " " +
"FROM " + tempTableNameEscaped +
") AND " +
orderColumnNameEscaped + " = (" +
"SELECT " + orderColumnNameEscaped + " " +
"FROM " + tempTableNameEscaped +
");"),
new UnparsedSql(
"DROP TABLE " + tempTableNameEscaped + ";")
};
} else if (database instanceof MSSQLDatabase) {
String changelogAliasEscaped = database.escapeObjectName("changelog", Table.class);
String latestAliasEscaped = database.escapeObjectName("latest", Table.class);
String idColumnEscaped = database.escapeObjectName("ID", Column.class);
String authorColumnEscaped = database.escapeObjectName("AUTHOR", Column.class);
String filenameColumnEscaped = database.escapeObjectName("FILENAME", Column.class);
String topClause = "TOP (1)";
try {
if (database.getDatabaseMajorVersion() < 10) {
// SQL Server 2005 or earlier
topClause = "TOP 1";
}
} catch (DatabaseException ignored) {
// assume SQL Server 2008 or later
}
return new Sql[] {
new UnparsedSql(
"UPDATE " + changelogAliasEscaped + " " +
"SET " + tagColumnNameEscaped + " = " + tagEscaped + " " +
"FROM " + tableNameEscaped + " AS " + changelogAliasEscaped + " " +
"INNER JOIN (" +
"SELECT " + topClause + " " + idColumnEscaped + ", " + authorColumnEscaped + ", " + filenameColumnEscaped + " " +
"FROM " + tableNameEscaped + " " +
"ORDER BY " + dateColumnNameEscaped + " DESC, " + orderColumnNameEscaped + " DESC" +
") AS " + latestAliasEscaped + " " +
"ON " + latestAliasEscaped + "." + idColumnEscaped + " = " + changelogAliasEscaped + "." + idColumnEscaped + " " +
"AND " + latestAliasEscaped + "." + authorColumnEscaped + " = " + changelogAliasEscaped + "." + authorColumnEscaped + " " +
"AND " + latestAliasEscaped + "." + filenameColumnEscaped + " = " + changelogAliasEscaped + "." + filenameColumnEscaped)
};
} else if (database instanceof OracleDatabase || database instanceof DB2Database) {
String selectClause = "SELECT";
String endClause = ")";
String delimiter = "";
if (database instanceof OracleDatabase) {
selectClause = "SELECT * FROM (SELECT";
endClause = ") where rownum=1)";
} else if (database instanceof DB2Database) {
endClause = " FETCH FIRST 1 ROWS ONLY)";
}
return new Sql[]{
new UnparsedSql("MERGE INTO " + tableNameEscaped + " a " +
"USING (" + selectClause + " " + orderColumnNameEscaped + ", " + dateColumnNameEscaped + " from " + tableNameEscaped + " order by " + dateColumnNameEscaped + " desc, " + orderColumnNameEscaped + " desc" + endClause + " b " +
"ON ( a." + dateColumnNameEscaped + " = b." + dateColumnNameEscaped + " and a." + orderColumnNameEscaped + "=b." + orderColumnNameEscaped + " ) " +
"WHEN MATCHED THEN " +
"UPDATE SET a.tag=" + tagEscaped + delimiter)
};
} else {
//Only uses dateexecuted as a default. Depending on the timestamp resolution, multiple rows may be tagged which normally works fine but can cause confusion and some issues.
//We cannot use orderexecuted alone because it is only guaranteed to be incrementing per update call.
//TODO: Better handle other databases to use dateexecuted desc, orderexecuted desc.
UpdateStatement updateStatement = new UpdateStatement(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogTableName())
.addNewColumnValue("TAG", statement.getTag())
.setWhereClause(
dateColumnNameEscaped + " = (" +
"SELECT MAX(" + dateColumnNameEscaped + ") " +
"FROM " + tableNameEscaped +
")");
return SqlGeneratorFactory.getInstance().generateSql(updateStatement, database);
}
}
}