liquibase.database.AbstractDatabase Maven / Gradle / Ivy
package liquibase.database;
import liquibase.change.Change;
import liquibase.change.CheckSum;
import liquibase.change.core.*;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.changelog.RanChangeSet;
import liquibase.changelog.filter.ContextChangeSetFilter;
import liquibase.changelog.filter.DbmsChangeSetFilter;
import liquibase.database.core.*;
import liquibase.database.structure.*;
import liquibase.diff.DiffStatusListener;
import liquibase.exception.*;
import liquibase.executor.Executor;
import liquibase.executor.ExecutorService;
import liquibase.logging.LogFactory;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.DatabaseSnapshotGeneratorFactory;
import liquibase.sql.Sql;
import liquibase.sql.visitor.SqlVisitor;
import liquibase.sqlgenerator.SqlGeneratorFactory;
import liquibase.statement.*;
import liquibase.statement.core.*;
import liquibase.util.ISODateFormat;
import liquibase.util.StreamUtil;
import liquibase.util.StringUtils;
import java.io.IOException;
import java.io.Writer;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;
/**
* AbstractDatabase is extended by all supported databases as a facade to the underlying database.
* The physical connection can be retrieved from the AbstractDatabase implementation, as well as any
* database-specific characteristics such as the datatype for "boolean" fields.
*/
public abstract class AbstractDatabase implements Database {
private DatabaseConnection connection;
private String defaultSchemaName;
protected String currentDateTimeFunction;
// List of Database native functions.
protected List databaseFunctions = new ArrayList();
private List ranChangeSetList;
private static Pattern CREATE_VIEW_AS_PATTERN = Pattern.compile("^CREATE\\s+.*?VIEW\\s+.*?AS\\s+", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
private String databaseChangeLogTableName = System.getProperty("liquibase.databaseChangeLogTableName") == null ? "DatabaseChangeLog".toUpperCase() : System.getProperty("liquibase.databaseChangeLogTableName");
private String databaseChangeLogLockTableName = System.getProperty("liquibase.databaseChangeLogLockTableName") == null ? "DatabaseChangeLogLock".toUpperCase() : System.getProperty("liquibase.databaseChangeLogLockTableName");
private String liquibaseSchemaName = System.getProperty("liquibase.schemaName") == null ? null : System.getProperty("liquibase.schemaName");
private Integer lastChangeSetSequenceValue;
private boolean canCacheLiquibaseTableInfo = false;
private boolean hasDatabaseChangeLogTable = false;
private boolean hasDatabaseChangeLogLockTable = false;
protected BigInteger defaultAutoIncrementStartWith = BigInteger.ONE;
protected BigInteger defaultAutoIncrementBy = BigInteger.ONE;
protected AbstractDatabase() {
}
public boolean requiresPassword() {
return true;
}
public boolean requiresUsername() {
return true;
}
public DatabaseObject[] getContainingObjects() {
return null;
}
// ------- DATABASE INFORMATION METHODS ---- //
public DatabaseConnection getConnection() {
return connection;
}
public void setConnection(DatabaseConnection conn) {
LogFactory.getLogger().debug("Connected to "+conn.getConnectionUserName()+"@"+conn.getURL());
this.connection = conn;
try {
connection.setAutoCommit(getAutoCommitMode());
} catch (DatabaseException sqle) {
LogFactory.getLogger().warning("Can not set auto commit to " + getAutoCommitMode() + " on connection");
}
}
/**
* Auto-commit mode to run in
*/
public boolean getAutoCommitMode() {
return !supportsDDLInTransaction();
}
/**
* By default databases should support DDL within a transaction.
*/
public boolean supportsDDLInTransaction() {
return true;
}
/**
* Returns the name of the database product according to the underlying database.
*/
public String getDatabaseProductName() {
if (connection == null) {
return null;
}
try {
return connection.getDatabaseProductName();
} catch (DatabaseException e) {
throw new RuntimeException("Cannot get database name");
}
}
public String getDatabaseProductVersion() throws DatabaseException {
if (connection == null) {
return null;
}
try {
return connection.getDatabaseProductVersion();
} catch (DatabaseException e) {
throw new DatabaseException(e);
}
}
public int getDatabaseMajorVersion() throws DatabaseException {
if (connection == null) {
return -1;
}
try {
return connection.getDatabaseMajorVersion();
} catch (DatabaseException e) {
throw new DatabaseException(e);
}
}
public int getDatabaseMinorVersion() throws DatabaseException {
if (connection == null) {
return -1;
}
try {
return connection.getDatabaseMinorVersion();
} catch (DatabaseException e) {
throw new DatabaseException(e);
}
}
public String getDefaultCatalogName() throws DatabaseException {
return null;
}
protected String getDefaultDatabaseSchemaName() throws DatabaseException {
return getConnection().getConnectionUserName();
}
public String getDefaultSchemaName() {
return defaultSchemaName;
}
public void setDefaultSchemaName(String schemaName) throws DatabaseException {
this.defaultSchemaName = schemaName;
}
/**
* Returns system (undroppable) tables and views.
*/
protected Set getSystemTablesAndViews() {
return new HashSet();
}
// ------- DATABASE FEATURE INFORMATION METHODS ---- //
/**
* Does the database type support sequence.
*/
public boolean supportsSequences() {
return true;
}
public boolean supportsAutoIncrement() {
return true;
}
// ------- DATABASE-SPECIFIC SQL METHODS ---- //
public void setCurrentDateTimeFunction(String function) {
if (function != null) {
this.currentDateTimeFunction = function;
}
}
/**
* Return a date literal with the same value as a string formatted using ISO 8601.
*
* Note: many databases accept date literals in ISO8601 format with the 'T' replaced with
* a space. Only databases which do not accept these strings should need to override this
* method.
*
* Implementation restriction:
* Currently, only the following subsets of ISO8601 are supported:
* yyyy-MM-dd
* hh:mm:ss
* yyyy-MM-ddThh:mm:ss
*/
public String getDateLiteral(String isoDate) {
if (isDateOnly(isoDate) || isTimeOnly(isoDate)) {
return "'" + isoDate + "'";
} else if (isDateTime(isoDate)) {
// StringBuffer val = new StringBuffer();
// val.append("'");
// val.append(isoDate.substring(0, 10));
// val.append(" ");
////noinspection MagicNumber
// val.append(isoDate.substring(11));
// val.append("'");
// return val.toString();
return "'" + isoDate.replace('T', ' ') + "'";
} else {
return "BAD_DATE_FORMAT:" + isoDate;
}
}
public String getDateTimeLiteral(java.sql.Timestamp date) {
return getDateLiteral(new ISODateFormat().format(date).replaceFirst("^'", "").replaceFirst("'$", ""));
}
public String getDateLiteral(java.sql.Date date) {
return getDateLiteral(new ISODateFormat().format(date).replaceFirst("^'", "").replaceFirst("'$", ""));
}
public String getTimeLiteral(java.sql.Time date) {
return getDateLiteral(new ISODateFormat().format(date).replaceFirst("^'", "").replaceFirst("'$", ""));
}
public String getDateLiteral(Date date) {
if (date instanceof java.sql.Date) {
return getDateLiteral(((java.sql.Date) date));
} else if (date instanceof java.sql.Time) {
return getTimeLiteral(((java.sql.Time) date));
} else if (date instanceof java.sql.Timestamp) {
return getDateTimeLiteral(((java.sql.Timestamp) date));
} else {
throw new RuntimeException("Unexpected type: " + date.getClass().getName());
}
}
public Date parseDate(String dateAsString) throws DateParseException {
try {
if (dateAsString.indexOf(" ") > 0) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAsString);
} else if (dateAsString.indexOf("T") > 0) {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(dateAsString);
} else {
if (dateAsString.indexOf(":") > 0) {
return new SimpleDateFormat("HH:mm:ss").parse(dateAsString);
} else {
return new SimpleDateFormat("yyyy-MM-dd").parse(dateAsString);
}
}
} catch (ParseException e) {
throw new DateParseException(dateAsString);
}
}
protected boolean isDateOnly(String isoDate) {
return isoDate.length() == "yyyy-MM-dd".length();
}
protected boolean isDateTime(String isoDate) {
return isoDate.length() >= "yyyy-MM-ddThh:mm:ss".length();
}
protected boolean isTimeOnly(String isoDate) {
return isoDate.length() == "hh:mm:ss".length();
}
/**
* Returns database-specific line comment string.
*/
public String getLineComment() {
return "--";
}
/**
* Returns database-specific auto-increment DDL clause.
*/
public String getAutoIncrementClause(BigInteger startWith, BigInteger incrementBy) {
if (!supportsAutoIncrement()) {
return "";
}
// generate an SQL:2003 standard compliant auto increment clause by default
String autoIncrementClause = getAutoIncrementClause();
boolean generateStartWith = generateAutoIncrementStartWith(startWith);
boolean generateIncrementBy = generateAutoIncrementBy(incrementBy);
if (generateStartWith || generateIncrementBy) {
autoIncrementClause += getAutoIncrementOpening();
if (generateStartWith) {
autoIncrementClause += String.format(getAutoIncrementStartWithClause(), startWith);
}
if (generateIncrementBy) {
if (generateStartWith) {
autoIncrementClause += ", ";
}
autoIncrementClause += String.format(getAutoIncrementByClause(), incrementBy);
}
autoIncrementClause += getAutoIncrementClosing();
}
return autoIncrementClause;
}
protected String getAutoIncrementClause() {
return "GENERATED BY DEFAULT AS IDENTITY";
}
protected boolean generateAutoIncrementStartWith(BigInteger startWith) {
return startWith != null
&& !startWith.equals(defaultAutoIncrementStartWith);
}
protected boolean generateAutoIncrementBy(BigInteger incrementBy) {
return incrementBy != null
&& !incrementBy.equals(defaultAutoIncrementBy);
}
protected String getAutoIncrementOpening() {
return " (";
}
protected String getAutoIncrementClosing() {
return ")";
}
protected String getAutoIncrementStartWithClause() {
return "START WITH %d";
}
protected String getAutoIncrementByClause() {
return "INCREMENT BY %d";
}
public String getConcatSql(String... values) {
StringBuffer returnString = new StringBuffer();
for (String value : values) {
returnString.append(value).append(" || ");
}
return returnString.toString().replaceFirst(" \\|\\| $", "");
}
// ------- DATABASECHANGELOG / DATABASECHANGELOGLOCK METHODS ---- //
/**
* @see liquibase.database.Database#getDatabaseChangeLogTableName()
*/
public String getDatabaseChangeLogTableName() {
return databaseChangeLogTableName;
}
/**
* @see liquibase.database.Database#getDatabaseChangeLogLockTableName()
*/
public String getDatabaseChangeLogLockTableName() {
return databaseChangeLogLockTableName;
}
/**
* @see liquibase.database.Database#setDatabaseChangeLogTableName(java.lang.String)
*/
public void setDatabaseChangeLogTableName(String tableName) {
this.databaseChangeLogTableName = tableName;
}
/**
* @see liquibase.database.Database#setDatabaseChangeLogLockTableName(java.lang.String)
*/
public void setDatabaseChangeLogLockTableName(String tableName) {
this.databaseChangeLogLockTableName = tableName;
}
/**
* This method will check the database ChangeLog table used to keep track of
* the changes in the file. If the table does not exist it will create one
* otherwise it will not do anything besides outputting a log message.
* @param updateExistingNullChecksums
* @param contexts
*/
public void checkDatabaseChangeLogTable(boolean updateExistingNullChecksums, DatabaseChangeLog databaseChangeLog, String... contexts) throws DatabaseException {
Executor executor = ExecutorService.getInstance().getExecutor(this);
Table changeLogTable = DatabaseSnapshotGeneratorFactory.getInstance().getGenerator(this).getDatabaseChangeLogTable(this);
List statementsToExecute = new ArrayList();
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 (!connection.getDatabaseProductName().equals("SQLite")) {
liquibaseColumnNotRightSize = changeLogTable.getColumn("LIQUIBASE").getColumnSize() != 20;
}
boolean hasOrderExecuted = changeLogTable.getColumn("ORDEREXECUTED") != null;
boolean checksumNotRightSize = false;
if (!connection.getDatabaseProductName().equals("SQLite")) {
checksumNotRightSize = changeLogTable.getColumn("MD5SUM").getColumnSize() != 35;
}
boolean hasExecTypeColumn = changeLogTable.getColumn("EXECTYPE") != null;
if (!hasDescription) {
executor.comment("Adding missing databasechangelog.description column");
statementsToExecute.add(new AddColumnStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName(), "DESCRIPTION", "VARCHAR(255)", null));
}
if (!hasTag) {
executor.comment("Adding missing databasechangelog.tag column");
statementsToExecute.add(new AddColumnStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName(), "TAG", "VARCHAR(255)", null));
}
if (!hasComments) {
executor.comment("Adding missing databasechangelog.comments column");
statementsToExecute.add(new AddColumnStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName(), "COMMENTS", "VARCHAR(255)", null));
}
if (!hasLiquibase) {
executor.comment("Adding missing databasechangelog.liquibase column");
statementsToExecute.add(new AddColumnStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName(), "LIQUIBASE", "VARCHAR(255)", null));
}
if (!hasOrderExecuted) {
executor.comment("Adding missing databasechangelog.orderexecuted column");
statementsToExecute.add(new AddColumnStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName(), "ORDEREXECUTED", "INT", null));
statementsToExecute.add(new UpdateStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName()).addNewColumnValue("ORDEREXECUTED", -1));
statementsToExecute.add(new SetNullableStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName(), "ORDEREXECUTED", "INT", false));
}
if (checksumNotRightSize) {
executor.comment("Modifying size of databasechangelog.md5sum column");
statementsToExecute.add(new ModifyDataTypeStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName(), "MD5SUM", "VARCHAR(35)"));
}
if (liquibaseColumnNotRightSize) {
executor.comment("Modifying size of databasechangelog.liquibase column");
statementsToExecute.add(new ModifyDataTypeStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName(), "LIQUIBASE", "VARCHAR(20)"));
}
if (!hasExecTypeColumn) {
executor.comment("Adding missing databasechangelog.exectype column");
statementsToExecute.add(new AddColumnStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName(), "EXECTYPE", "VARCHAR(10)", null));
statementsToExecute.add(new UpdateStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName()).addNewColumnValue("EXECTYPE", "EXECUTED"));
statementsToExecute.add(new SetNullableStatement(getLiquibaseSchemaName(), getDatabaseChangeLogTableName(), "EXECTYPE", "VARCHAR(10)", false));
}
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy