@Override public void setConnection(final DatabaseConnection conn) { LogFactory.getLogger() .debug("Connected to " + conn.getConnectionUserName() + "@" + conn.getURL()); this.connection = conn; try { boolean autoCommit = conn.getAutoCommit(); if (autoCommit == getAutoCommitMode()) { // Don't adjust the auto-commit mode if it's already what the database wants it to be. LogFactory.getLogger() .debug("Not adjusting the auto commit mode; it is already " + autoCommit); } else { // Store the previous auto-commit mode, because the connection needs to be restored to it // when this // AbstractDatabase type is closed. This is important for systems which use connection // pools. previousAutoCommit = autoCommit; LogFactory.getLogger() .debug("Setting auto commit to " + getAutoCommitMode() + " from " + autoCommit); connection.setAutoCommit(getAutoCommitMode()); } } catch (DatabaseException e) { LogFactory.getLogger() .warning("Cannot set auto commit to " + getAutoCommitMode() + " on connection"); } this.connection.attached(this); }
@Override public void setConnection(DatabaseConnection conn) { reservedWords.addAll( Arrays.asList( "GROUP", "USER", "SESSION", "PASSWORD", "RESOURCE", "START", "SIZE", "UID", "DESC")); // more reserved words not returned by driver Connection sqlConn = null; if (!(conn instanceof OfflineConnection)) { try { /** * Don't try to call getWrappedConnection if the conn instance is is not a JdbcConnection. * This happens for OfflineConnection. * * @see <a href="https://liquibase.jira.com/browse/CORE-2192">CORE-2192</a> */ if (conn instanceof JdbcConnection) { Method wrappedConn = conn.getClass().getMethod("getWrappedConnection"); wrappedConn.setAccessible(true); sqlConn = (Connection) wrappedConn.invoke(conn); } } catch (Exception e) { throw new UnexpectedLiquibaseException(e); } if (sqlConn != null) { try { reservedWords.addAll( Arrays.asList(sqlConn.getMetaData().getSQLKeywords().toUpperCase().split(",\\s*"))); } catch (SQLException e) { LogFactory.getLogger() .info("Could get sql keywords on OracleDatabase: " + e.getMessage()); // can not get keywords. Continue on } try { Method method = sqlConn.getClass().getMethod("setRemarksReporting", Boolean.TYPE); method.setAccessible(true); method.invoke(sqlConn, true); } catch (Exception e) { LogFactory.getLogger() .info("Could not set remarks reporting on OracleDatabase: " + e.getMessage()); ; // cannot set it. That is OK } } } super.setConnection(conn); }
@Override 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"); } }
/** Returns the ChangeSets that have been run against the current database. */ @Override public List<RanChangeSet> getRanChangeSetList() throws DatabaseException { if (this.ranChangeSetList != null) { return this.ranChangeSetList; } String databaseChangeLogTableName = escapeTableName(getLiquibaseSchemaName(), getDatabaseChangeLogTableName()); ranChangeSetList = new ArrayList<RanChangeSet>(); if (hasDatabaseChangeLogTable()) { LogFactory.getLogger().info("Reading from " + databaseChangeLogTableName); SqlStatement select = new SelectFromDatabaseChangeLogStatement( "FILENAME", "AUTHOR", "ID", "MD5SUM", "DATEEXECUTED", "ORDEREXECUTED", "TAG", "EXECTYPE") .setOrderBy("DATEEXECUTED ASC", "ORDEREXECUTED ASC"); List<Map> results = ExecutorService.getInstance().getExecutor(this).queryForList(select); for (Map<?, ?> rs : results) { String fileName = rs.get("FILENAME").toString(); String author = rs.get("AUTHOR").toString(); String id = rs.get("ID").toString(); String md5sum = rs.get("MD5SUM") == null ? null : rs.get("MD5SUM").toString(); Date dateExecuted = (Date) rs.get("DATEEXECUTED"); String tag = rs.get("TAG") == null ? null : rs.get("TAG").toString(); String execType = rs.get("EXECTYPE") == null ? null : rs.get("EXECTYPE").toString(); try { RanChangeSet ranChangeSet = new RanChangeSet( fileName, id, author, CheckSum.parse(md5sum), dateExecuted, tag, ChangeSet.ExecType.valueOf(execType)); ranChangeSetList.add(ranChangeSet); } catch (IllegalArgumentException e) { LogFactory.getLogger().severe("Unknown EXECTYPE from database: " + execType); throw e; } } } return ranChangeSetList; }
@Override public void waitForLock() throws LockException { boolean locked = false; long timeToGiveUp = new Date().getTime() + (getChangeLogLockWaitTime() * 1000 * 60); while (!locked && new Date().getTime() < timeToGiveUp) { locked = acquireLock(); if (!locked) { LogFactory.getLogger().info("Waiting for changelog lock...."); try { Thread.sleep(getChangeLogLockRecheckTime() * 1000); } catch (InterruptedException e) {; } } } if (!locked) { DatabaseChangeLogLock[] locks = listLocks(); String lockedBy; if (locks.length > 0) { DatabaseChangeLogLock lock = locks[0]; lockedBy = lock.getLockedBy() + " since " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) .format(lock.getLockGranted()); } else { lockedBy = "UNKNOWN"; } throw new LockException( "Could not acquire change log lock. Currently locked by " + lockedBy); } }
@Override public void finishInitialization() throws SetupException { if (path == null) { throw new SetupException("<sqlfile> - No path specified"); } LogFactory.getLogger().debug("SQLFile file:" + path); boolean loaded = loadFromClasspath(path); if (!loaded) { loaded = loadFromFileSystem(path); } if (!loaded) { throw new SetupException("<sqlfile path=" + path + "> - Could not find file"); } LogFactory.getLogger().debug("SQLFile file contents is:" + getSql()); }
@Override public void setConnection(DatabaseConnection conn) { try { Method wrappedConn = conn.getClass().getMethod("getWrappedConnection"); wrappedConn.setAccessible(true); Connection sqlConn = (Connection) wrappedConn.invoke(conn); Method method = sqlConn.getClass().getMethod("setRemarksReporting", Boolean.TYPE); method.setAccessible(true); method.invoke(sqlConn, true); reservedWords.addAll( Arrays.asList(sqlConn.getMetaData().getSQLKeywords().toUpperCase().split(",\\s*"))); reservedWords.addAll( Arrays.asList( "GROUP", "USER", "SESSION", "PASSWORD", "RESOURCE", "START", "SIZE", "UID")); // more reserved words not returned by driver } catch (Exception e) { LogFactory.getLogger() .info("Could not set remarks reporting on OracleDatabase: " + e.getMessage()); ; // cannot set it. That is OK } super.setConnection(conn); }
public void releaseLock() throws DatabaseException, LockException { Executor executor = ExecutorService.getInstance().getExecutor(database); try { if (database.hasDatabaseChangeLogLockTable()) { executor.comment("Release Database Lock"); database.rollback(); int updatedRows = executor.update(new UnlockDatabaseChangeLogStatement()); if (updatedRows != 1) { throw new LockException( "Did not update change log lock correctly.\n\n" + updatedRows + " rows were updated instead of the expected 1 row using executor " + executor.getClass().getName() + " there are " + executor.queryForInt( new RawSqlStatement( "select count(*) from " + database.getDatabaseChangeLogLockTableName())) + " rows in the table"); } database.commit(); hasChangeLogLock = false; instances.remove(this.database); database.setCanCacheLiquibaseTableInfo(false); LogFactory.getLogger().info("Successfully released change log lock"); } } finally { database.rollback(); } }
@Override public void init() throws DatabaseException { boolean createdTable = false; Executor executor = ExecutorService.getInstance().getExecutor(database); if (!hasDatabaseChangeLogLockTable()) { executor.comment("Create Database Lock Table"); executor.execute(new CreateDatabaseChangeLogLockTableStatement()); database.commit(); LogFactory.getLogger() .debug( "Created database lock table with name: " + database.escapeTableName( database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName())); this.hasDatabaseChangeLogLockTable = true; createdTable = true; } if (!isDatabaseChangeLogLockTableInitialized(createdTable)) { executor.comment("Initialize Database Lock Table"); executor.execute(new InitializeDatabaseChangeLogLockTableStatement()); database.commit(); } if (database instanceof DerbyDatabase && ((DerbyDatabase) database) .supportsBooleanDataType()) { // check if the changelog table is of an old smallint vs. // boolean format String lockTable = database.escapeTableName( database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName()); Object obj = executor.queryForObject( new RawSqlStatement( "select min(locked) as test from " + lockTable + " fetch first row only"), Object.class); if (!(obj instanceof Boolean)) { // wrong type, need to recreate table executor.execute( new DropTableStatement( database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName(), false)); executor.execute(new CreateDatabaseChangeLogLockTableStatement()); executor.execute(new InitializeDatabaseChangeLogLockTableStatement()); } } }
/** Returns the run status for the given ChangeSet */ @Override public ChangeSet.RunStatus getRunStatus(ChangeSet changeSet) throws DatabaseException, DatabaseHistoryException { if (!hasDatabaseChangeLogTable()) { return ChangeSet.RunStatus.NOT_RAN; } RanChangeSet foundRan = getRanChangeSet(changeSet); if (foundRan == null) { return ChangeSet.RunStatus.NOT_RAN; } else { if (foundRan.getLastCheckSum() == null) { try { LogFactory.getLogger().info("Updating NULL md5sum for " + changeSet.toString()); ExecutorService.getInstance() .getExecutor(this) .execute( new RawSqlStatement( "UPDATE " + escapeTableName( getLiquibaseSchemaName(), getDatabaseChangeLogTableName()) + " SET MD5SUM='" + changeSet.generateCheckSum().toString() + "' WHERE ID='" + changeSet.getId() + "' AND AUTHOR='" + changeSet.getAuthor() + "' AND FILENAME='" + changeSet.getFilePath() + "'")); this.commit(); } catch (DatabaseException e) { throw new DatabaseException(e); } return ChangeSet.RunStatus.ALREADY_RAN; } else { if (foundRan.getLastCheckSum().equals(changeSet.generateCheckSum())) { return ChangeSet.RunStatus.ALREADY_RAN; } else { if (changeSet.shouldRunOnChange()) { return ChangeSet.RunStatus.RUN_AGAIN; } else { return ChangeSet.RunStatus.INVALID_MD5SUM; // throw new DatabaseHistoryException("MD5 Check for " + changeSet.toString() + " // failed"); } } } } }
/* * Executes the statements passed * * @param statements an array containing the SQL statements to be issued * @param sqlVisitors a list of {@link SqlVisitor} objects to be applied to the executed statements * @throws DatabaseException if there were problems issuing the statements */ @Override public void execute(final SqlStatement[] statements, final List<SqlVisitor> sqlVisitors) throws LiquibaseException { for (SqlStatement statement : statements) { if (statement.skipOnUnsupported() && !SqlGeneratorFactory.getInstance().supports(statement, this)) { continue; } LogFactory.getLogger().debug("Executing Statement: " + statement); ExecutorService.getInstance().getExecutor(this).execute(statement, sqlVisitors); } }
/** * Overwrite this method to get the default schema name for the connection. * * @return */ protected String getConnectionSchemaName() { if (connection == null || connection instanceof OfflineConnection) { return null; } try { return ExecutorService.getInstance() .getExecutor(this) .queryForObject(new RawCallStatement("call current_schema"), String.class); } catch (Exception e) { LogFactory.getLogger().info("Error getting default schema", e); } return null; }
@Override public boolean acquireLock() throws LockException { if (hasChangeLogLock) { return true; } Executor executor = ExecutorService.getInstance().getExecutor(database); try { database.rollback(); this.init(); Boolean locked = (Boolean) ExecutorService.getInstance() .getExecutor(database) .queryForObject( new SelectFromDatabaseChangeLogLockStatement("LOCKED"), Boolean.class); if (locked) { return false; } else { executor.comment("Lock Database"); int rowsUpdated = executor.update(new LockDatabaseChangeLogStatement()); if (rowsUpdated > 1) { throw new LockException("Did not update change log lock correctly"); } if (rowsUpdated == 0) { // another node was faster return false; } database.commit(); LogFactory.getLogger().info("Successfully acquired change log lock"); hasChangeLogLock = true; database.setCanCacheLiquibaseTableInfo(true); return true; } } catch (Exception e) { throw new LockException(e); } finally { try { database.rollback(); } catch (DatabaseException e) {; } } }
/** * This method will check the database ChangeLogLock table used to keep track of if a machine is * updating the database. If the table does not exist it will create one otherwise it will not do * anything besides outputting a log message. */ @Override public void checkDatabaseChangeLogLockTable() throws DatabaseException { Executor executor = ExecutorService.getInstance().getExecutor(this); if (!hasDatabaseChangeLogLockTable()) { executor.comment("Create Database Lock Table"); executor.execute(new CreateDatabaseChangeLogLockTableStatement()); this.commit(); LogFactory.getLogger() .debug( "Created database lock table with name: " + escapeTableName(getLiquibaseSchemaName(), getDatabaseChangeLogLockTableName())); this.hasDatabaseChangeLogLockTable = true; } }
public void setUp(String contexts) { try { ResourceAccessor resourceAccessor = new FileSystemResourceAccessor(); Class.forName("org.hsqldb.jdbcDriver"); holdingConnection = getConnectionImpl(); HsqlConnection hsconn = new HsqlConnection(holdingConnection); LogFactory.getLogger().setLogLevel("severe"); liquibase = new Liquibase(CHANGE_LOG, resourceAccessor, hsconn); liquibase.dropAll(); liquibase.update(contexts); hsconn.close(); } catch (Exception e) { LOG.error("Error during database initialization", e); throw new RuntimeException("Error during database initialization", e); } }
@Override protected String getConnectionCatalogName() throws DatabaseException { if (getConnection() instanceof OfflineConnection) { return getConnection().getCatalog(); } try { return ExecutorService.getInstance() .getExecutor(this) .queryForObject( new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class); } catch (Exception e) { LogFactory.getLogger().info("Error getting default schema", e); } return null; }
@Override public String getDefaultCatalogName() { if (defaultCatalogName == null) { if (defaultSchemaName != null && !this.supportsSchemas()) { return defaultSchemaName; } if (connection != null) { try { defaultCatalogName = getConnectionCatalogName(); } catch (DatabaseException e) { LogFactory.getLogger().info("Error getting default catalog", e); } } } return defaultCatalogName; }
@Override public void close() throws DatabaseException { DatabaseConnection connection = getConnection(); if (connection != null) { if (previousAutoCommit != null) { try { connection.setAutoCommit(previousAutoCommit); } catch (DatabaseException e) { LogFactory.getLogger() .warning("Failed to restore the auto commit to " + previousAutoCommit); throw e; } } connection.close(); } ExecutorService.getInstance().clearExecutor(this); }
static { try { Class<?> scanner = Class.forName("Liquibase.ServiceLocator.ClrServiceLocator, Liquibase"); instance = (ServiceLocator) scanner.newInstance(); } catch (Exception e) { try { if (OSGiUtil.isLiquibaseLoadedAsOSGiBundle()) { Bundle liquibaseBundle = FrameworkUtil.getBundle(ServiceLocator.class); instance = new ServiceLocator( new OSGiPackageScanClassResolver(liquibaseBundle), new OSGiResourceAccessor(liquibaseBundle)); } else { instance = new ServiceLocator(); } } catch (Throwable e1) { LogFactory.getInstance().getLog().severe("Cannot build ServiceLocator", e1); } } }
private static class IOThread extends Thread { private Logger log = LogFactory.getInstance().getLog(); private InputStream from; private OutputStream to; public IOThread(InputStream from, OutputStream to) { super(); setDaemon(true); this.from = from; this.to = to; } @Override public void run() { try { StreamUtil.copy(from, to); } catch (IOException e) { log.debug("While copying streams", e); } } }
@Override public boolean isCaseSensitive() { if (caseSensitive == null) { if (connection != null && connection instanceof JdbcConnection) { try { caseSensitive = ((JdbcConnection) connection) .getUnderlyingConnection() .getMetaData() .supportsMixedCaseIdentifiers(); } catch (SQLException e) { LogFactory.getLogger().warning("Cannot determine case sensitivity from JDBC driver", e); } } } if (caseSensitive == null) { return false; } else { return caseSensitive.booleanValue(); } }
public ValidationErrors validate(Database database) { ValidationErrors changeValidationErrors = new ValidationErrors(); for (SqlStatement statement : generateStatements(database)) { boolean supported = SqlGeneratorFactory.getInstance().supports(statement, database); if (!supported) { if (statement.skipOnUnsupported()) { LogFactory.getLogger() .info( getChangeMetaData().getName() + " is not supported on " + database.getTypeName() + " but will continue"); } else { changeValidationErrors.addError( getChangeMetaData().getName() + " is not supported on " + database.getTypeName()); } } else { changeValidationErrors.addAll( SqlGeneratorFactory.getInstance().validate(statement, database)); } } return changeValidationErrors; }
protected DataType readDataType( CachedRow columnMetadataResultSet, Column column, Database database) throws SQLException { if (database instanceof OracleDatabase) { String dataType = columnMetadataResultSet.getString("DATA_TYPE"); dataType = dataType.replace("VARCHAR2", "VARCHAR"); dataType = dataType.replace("NVARCHAR2", "NVARCHAR"); DataType type = new DataType(dataType); // type.setDataTypeId(dataType); if (dataType.equalsIgnoreCase("NUMBER")) { type.setColumnSize(columnMetadataResultSet.getInt("DATA_PRECISION")); // if (type.getColumnSize() == null) { // type.setColumnSize(38); // } type.setDecimalDigits(columnMetadataResultSet.getInt("DATA_SCALE")); // if (type.getDecimalDigits() == null) { // type.setDecimalDigits(0); // } // type.setRadix(10); } else { type.setColumnSize(columnMetadataResultSet.getInt("DATA_LENGTH")); if (dataType.equalsIgnoreCase("NCLOB") || dataType.equalsIgnoreCase("BLOB") || dataType.equalsIgnoreCase("CLOB")) { type.setColumnSize(null); } else if (dataType.equalsIgnoreCase("NVARCHAR") || dataType.equalsIgnoreCase("NCHAR")) { type.setColumnSize(columnMetadataResultSet.getInt("CHAR_LENGTH")); type.setColumnSizeUnit(DataType.ColumnSizeUnit.CHAR); } else { String charUsed = columnMetadataResultSet.getString("CHAR_USED"); DataType.ColumnSizeUnit unit = null; if ("C".equals(charUsed)) { unit = DataType.ColumnSizeUnit.CHAR; type.setColumnSize(columnMetadataResultSet.getInt("CHAR_LENGTH")); } type.setColumnSizeUnit(unit); } } return type; } String columnTypeName = (String) columnMetadataResultSet.get("TYPE_NAME"); if (database instanceof FirebirdDatabase) { if (columnTypeName.equals("BLOB SUB_TYPE 0")) { columnTypeName = "BLOB"; } if (columnTypeName.equals("BLOB SUB_TYPE 1")) { columnTypeName = "CLOB"; } } if (database instanceof MySQLDatabase && (columnTypeName.equalsIgnoreCase("ENUM") || columnTypeName.equalsIgnoreCase("SET"))) { try { String boilerLength; if (columnTypeName.equalsIgnoreCase("ENUM")) boilerLength = "7"; else // SET boilerLength = "6"; List<String> enumValues = ExecutorService.getInstance() .getExecutor(database) .queryForList( new RawSqlStatement( "SELECT DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING(COLUMN_TYPE, " + boilerLength + ", LENGTH(COLUMN_TYPE) - " + boilerLength + " - 1 ), \"','\", 1 + units.i + tens.i * 10) , \"','\", -1)\n" + "FROM INFORMATION_SCHEMA.COLUMNS\n" + "CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units\n" + "CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens\n" + "WHERE TABLE_NAME = '" + column.getRelation().getName() + "' \n" + "AND COLUMN_NAME = '" + column.getName() + "'"), String.class); String enumClause = ""; for (String enumValue : enumValues) { enumClause += "'" + enumValue + "', "; } enumClause = enumClause.replaceFirst(", $", ""); return new DataType(columnTypeName + "(" + enumClause + ")"); } catch (DatabaseException e) { LogFactory.getLogger().warning("Error fetching enum values", e); } } DataType.ColumnSizeUnit columnSizeUnit = DataType.ColumnSizeUnit.BYTE; int dataType = columnMetadataResultSet.getInt("DATA_TYPE"); Integer columnSize = null; Integer decimalDigits = null; if (!database.dataTypeIsNotModifiable( columnTypeName)) { // don't set size for types like int4, int8 etc columnSize = columnMetadataResultSet.getInt("COLUMN_SIZE"); decimalDigits = columnMetadataResultSet.getInt("DECIMAL_DIGITS"); if (decimalDigits != null && decimalDigits.equals(0)) { decimalDigits = null; } } Integer radix = columnMetadataResultSet.getInt("NUM_PREC_RADIX"); Integer characterOctetLength = columnMetadataResultSet.getInt("CHAR_OCTET_LENGTH"); if (database instanceof DB2Database) { String typeName = columnMetadataResultSet.getString("TYPE_NAME"); if (typeName.equalsIgnoreCase("DBCLOB") || typeName.equalsIgnoreCase("GRAPHIC") || typeName.equalsIgnoreCase("VARGRAPHIC")) { if (columnSize != null) { columnSize = columnSize / 2; // Stored as double length chars } } } DataType type = new DataType(columnTypeName); type.setDataTypeId(dataType); type.setColumnSize(columnSize); type.setDecimalDigits(decimalDigits); type.setRadix(radix); type.setCharacterOctetLength(characterOctetLength); type.setColumnSizeUnit(columnSizeUnit); return type; }
/** Statement to run {@code pt-online-schema-change} in order to alter a database table. */ public class PTOnlineSchemaChangeStatement extends RuntimeStatement { public static final String COMMAND = "pt-online-schema-change"; private static String perconaToolkitVersion = null; static Boolean available = null; private static Logger log = LogFactory.getInstance().getLog(); private String tableName; private String alterStatement; public PTOnlineSchemaChangeStatement(String tableName, String alterStatement) { this.tableName = tableName; this.alterStatement = alterStatement; } /** * Tokenizes the given options into separate arguments, so that it can be fed into the {@link * ProcessBuilder}'s commands. * * @param options the options as one single string * @return the list of arguments */ private List<String> tokenize(String options) { StringTokenizer stringTokenizer = new StringTokenizer(options); List<String> result = new LinkedList<String>(); while (stringTokenizer.hasMoreTokens()) { result.add(stringTokenizer.nextToken()); } return joinQuotedArguments(result); } /** * Very simplistic approach to join together any quoted arguments. Only double quotes are * supported and the join character is a space. * * @param tokenizedArguments the arguments tokenized by space * @return the filtered arguments, maybe joined */ private List<String> joinQuotedArguments(List<String> tokenizedArguments) { final String joinCharacters = " "; List<String> filtered = new LinkedList<String>(); boolean inQuotes = false; for (int i = 0; i < tokenizedArguments.size(); i++) { String arg = tokenizedArguments.get(i); if (!inQuotes) { if (arg.startsWith("\"")) { inQuotes = true; arg = arg.substring(1); } if (arg.endsWith("\"")) { inQuotes = false; arg = arg.substring(0, arg.length() - 1); } filtered.add(arg); } else { if (arg.endsWith("\"")) { inQuotes = false; arg = arg.substring(0, arg.length() - 1); } String last = filtered.get(filtered.size() - 1); filtered.set(filtered.size() - 1, last + joinCharacters + arg); } } return filtered; } /** * Builds the command line arguments that will be executed. * * @param database the database - needed to get the connection info. * @return the command line arguments including {@link #COMMAND} */ List<String> buildCommand(Database database) { List<String> commands = new ArrayList<String>(); commands.add(PTOnlineSchemaChangeStatement.COMMAND); if (!Configuration.getAdditionalOptions().isEmpty()) { commands.addAll(tokenize(Configuration.getAdditionalOptions())); } commands.add("--alter=" + alterStatement); commands.add("--alter-foreign-keys-method=auto"); if (database.getConnection() != null) { DatabaseConnectionUtil connection = new DatabaseConnectionUtil(database.getConnection()); commands.add("--host=" + connection.getHost()); commands.add("--port=" + connection.getPort()); commands.add("--user="******"--password="******"--execute"); commands.add("D=" + database.getLiquibaseSchemaName() + ",t=" + tableName); return commands; } /** * Generates the command line that would be executed and return it as a single string. The * password will be masked. * * @param database the database - needed to get the connection info * @return the string */ public String printCommand(Database database) { List<String> command = buildCommand(database); return filterCommands(command); } /** * Converts the given command list into a single string and mask the password * * @param command the command line arguments that would be used for pt-online-schema-change * @return the string with masked password */ private String filterCommands(List<String> command) { StringBuilder sb = new StringBuilder(); for (String s : command) { sb.append(" "); if (s.startsWith("--password")) { sb.append("--password=***"); } else if (s.contains(" ")) { sb.append(s.substring(0, s.indexOf('=') + 1)) .append("\"") .append(s.substring(s.indexOf('=') + 1)) .append("\""); } else { sb.append(s); } } return sb.substring(1).toString(); } /** * Actually executes pt-online-schema change. Does not generate any Sql. * * @return always <code>null</code> */ @Override public Sql[] generate(Database database) { List<String> cmndline = buildCommand(database); log.info("Executing: " + filterCommands(cmndline)); ProcessBuilder pb = new ProcessBuilder(cmndline); pb.redirectErrorStream(true); Process p = null; final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final OutputStream tee = new FilterOutputStream(outputStream) { @Override public void write(int b) throws IOException { if (b == '\n') { log.info(outputStream.toString(Charset.defaultCharset().toString())); outputStream.reset(); } else { super.write(b); } } }; try { p = pb.start(); final InputStream in = p.getInputStream(); final InputStream err = p.getErrorStream(); IOThread reader = new IOThread(in, tee); IOThread reader2 = new IOThread(err, tee); reader.start(); reader2.start(); int exitCode = p.waitFor(); reader.join(5000); reader2.join(5000); // log the remaining output log.info(outputStream.toString(Charset.defaultCharset().toString())); if (exitCode != 0) { throw new RuntimeException("Percona exited with " + exitCode); } } catch (IOException e) { throw new UnexpectedLiquibaseException(e); } catch (InterruptedException e) { throw new UnexpectedLiquibaseException(e); } finally { if (p != null) { StreamUtil.closeQuietly(p.getErrorStream()); StreamUtil.closeQuietly(p.getInputStream()); StreamUtil.closeQuietly(p.getOutputStream()); p.destroy(); } StreamUtil.closeQuietly(outputStream); } return null; } @Override public String toString() { return PTOnlineSchemaChangeStatement.class.getSimpleName() + "[table: " + tableName + ", alterStatement: " + alterStatement + "]"; } private static class IOThread extends Thread { private Logger log = LogFactory.getInstance().getLog(); private InputStream from; private OutputStream to; public IOThread(InputStream from, OutputStream to) { super(); setDaemon(true); this.from = from; this.to = to; } @Override public void run() { try { StreamUtil.copy(from, to); } catch (IOException e) { log.debug("While copying streams", e); } } } public static String getVersion() { checkIsAvailableAndGetVersion(); return perconaToolkitVersion; } /** * Checks whether the command is available and can be started. * * <p><em>Implementation detail:</em> This is lazily detected once and then cached. * * @return <code>true</code> if it is available and executable, <code>false</code> otherwise * @see #COMMAND */ public static synchronized boolean isAvailable() { if (available != null) { return available.booleanValue(); } checkIsAvailableAndGetVersion(); return available.booleanValue(); } private static void checkIsAvailableAndGetVersion() { ProcessBuilder pb = new ProcessBuilder(COMMAND, "--version"); pb.redirectErrorStream(true); Process p = null; try { p = pb.start(); p.waitFor(); perconaToolkitVersion = StreamUtil.getStreamContents(p.getInputStream()); if (perconaToolkitVersion != null) { perconaToolkitVersion = perconaToolkitVersion.replaceAll("\n|\r", ""); } available = true; log.info("Using percona toolkit: " + perconaToolkitVersion); } catch (IOException e) { available = false; } catch (InterruptedException e) { available = false; } finally { if (p != null) { StreamUtil.closeQuietly(p.getErrorStream()); StreamUtil.closeQuietly(p.getInputStream()); StreamUtil.closeQuietly(p.getOutputStream()); p.destroy(); } } } }
protected Column readColumn(CachedRow columnMetadataResultSet, Relation table, Database database) throws SQLException, DatabaseException { String rawTableName = (String) columnMetadataResultSet.get("TABLE_NAME"); String rawColumnName = (String) columnMetadataResultSet.get("COLUMN_NAME"); String rawSchemaName = StringUtils.trimToNull((String) columnMetadataResultSet.get("TABLE_SCHEM")); String rawCatalogName = StringUtils.trimToNull((String) columnMetadataResultSet.get("TABLE_CAT")); String remarks = StringUtils.trimToNull((String) columnMetadataResultSet.get("REMARKS")); if (remarks != null) { remarks = remarks.replace("''", "'"); // come back escaped sometimes } Column column = new Column(); column.setName(StringUtils.trimToNull(rawColumnName)); column.setRelation(table); column.setRemarks(remarks); if (database instanceof OracleDatabase) { String nullable = columnMetadataResultSet.getString("NULLABLE"); if (nullable.equals("Y")) { column.setNullable(true); } else { column.setNullable(false); } } else { int nullable = columnMetadataResultSet.getInt("NULLABLE"); if (nullable == DatabaseMetaData.columnNoNulls) { column.setNullable(false); } else if (nullable == DatabaseMetaData.columnNullable) { column.setNullable(true); } else if (nullable == DatabaseMetaData.columnNullableUnknown) { LogFactory.getLogger() .info("Unknown nullable state for column " + column.toString() + ". Assuming nullable"); column.setNullable(true); } } if (database.supportsAutoIncrement()) { if (table instanceof Table) { if (columnMetadataResultSet.containsColumn("IS_AUTOINCREMENT")) { String isAutoincrement = (String) columnMetadataResultSet.get("IS_AUTOINCREMENT"); isAutoincrement = StringUtils.trimToNull(isAutoincrement); if (isAutoincrement == null) { column.setAutoIncrementInformation(null); } else if (isAutoincrement.equals("YES")) { column.setAutoIncrementInformation(new Column.AutoIncrementInformation()); } else if (isAutoincrement.equals("NO")) { column.setAutoIncrementInformation(null); } else if (isAutoincrement.equals("")) { LogFactory.getLogger() .info( "Unknown auto increment state for column " + column.toString() + ". Assuming not auto increment"); column.setAutoIncrementInformation(null); } else { throw new UnexpectedLiquibaseException( "Unknown is_autoincrement value: '" + isAutoincrement + "'"); } } else { // probably older version of java, need to select from the column to find out if it is // auto-increment String selectStatement; if (database.getDatabaseProductName().startsWith("DB2 UDB for AS/400")) { selectStatement = "select " + database.escapeColumnName( rawCatalogName, rawSchemaName, rawTableName, rawColumnName) + " from " + rawSchemaName + "." + rawTableName + " where 0=1"; LogFactory.getLogger().debug("rawCatalogName : <" + rawCatalogName + ">"); LogFactory.getLogger().debug("rawSchemaName : <" + rawSchemaName + ">"); LogFactory.getLogger().debug("rawTableName : <" + rawTableName + ">"); LogFactory.getLogger().debug("raw selectStatement : <" + selectStatement + ">"); } else { selectStatement = "select " + database.escapeColumnName( rawCatalogName, rawSchemaName, rawTableName, rawColumnName) + " from " + database.escapeTableName(rawCatalogName, rawSchemaName, rawTableName) + " where 0=1"; } LogFactory.getLogger() .debug( "Checking " + rawTableName + "." + rawCatalogName + " for auto-increment with SQL: '" + selectStatement + "'"); Connection underlyingConnection = ((JdbcConnection) database.getConnection()).getUnderlyingConnection(); Statement statement = null; ResultSet columnSelectRS = null; try { statement = underlyingConnection.createStatement(); columnSelectRS = statement.executeQuery(selectStatement); if (columnSelectRS.getMetaData().isAutoIncrement(1)) { column.setAutoIncrementInformation(new Column.AutoIncrementInformation()); } else { column.setAutoIncrementInformation(null); } } finally { try { if (statement != null) { statement.close(); } } catch (SQLException ignore) { } if (columnSelectRS != null) { columnSelectRS.close(); } } } } } DataType type = readDataType(columnMetadataResultSet, column, database); column.setType(type); column.setDefaultValue(readDefaultValue(columnMetadataResultSet, column, database)); return column; }
@Override public Sql[] generateSql( CreateTempTableStatementVertica statement, Database database, SqlGeneratorChain sqlGeneratorChain) { StringBuilder sql = new StringBuilder(); sql.append("CREATE "); if (statement.getIsGlobal() != null) sql.append(" GLOBAL "); else sql.append(" LOCAL "); sql.append(" TEMPORARY TABLE "); if (statement.getSchemaName() != null) sql.append(statement.getSchemaName()).append("."); else sql.append(database.getDefaultSchemaName()).append("."); if (statement.getTableName() != null) { sql.append(statement.getTableName()).append(" "); } boolean isSinglePrimaryKeyColumn = statement.getPrimaryKeyConstraint() != null && statement.getPrimaryKeyConstraint().getColumns().size() == 1; boolean isPrimaryKeyAutoIncrement = false; sql.append("( "); Iterator<String> columnIterator = statement.getColumns().iterator(); List<String> primaryKeyColumns = new LinkedList<String>(); while (columnIterator.hasNext()) { String column = columnIterator.next(); DatabaseDataType columnType = statement.getColumnTypes().get(column).toDatabaseDataType(database); sql.append( database.escapeColumnName( statement.getCatalogName(), statement.getSchemaName(), statement.getTableName(), column)); // This is the difference between vertica & other RDBMS - the encoding part. AutoIncrementConstraint autoIncrementConstraint = null; for (AutoIncrementConstraint currentAutoIncrementConstraint : statement.getAutoIncrementConstraints()) { if (column.equals(currentAutoIncrementConstraint.getColumnName())) { autoIncrementConstraint = currentAutoIncrementConstraint; break; } } boolean isAutoIncrementColumn = autoIncrementConstraint != null; boolean isPrimaryKeyColumn = statement.getPrimaryKeyConstraint() != null && statement.getPrimaryKeyConstraint().getColumns().contains(column); isPrimaryKeyAutoIncrement = isPrimaryKeyAutoIncrement || isPrimaryKeyColumn && isAutoIncrementColumn; if (isPrimaryKeyColumn) { primaryKeyColumns.add(column); } if (!isAutoIncrementColumn) { sql.append(" ").append(columnType); } // for the serial data type in postgres, there should be no default value if (!columnType.isAutoIncrement() && statement.getDefaultValue(column) != null) { Object defaultValue = statement.getDefaultValue(column); sql.append(" DEFAULT "); sql.append(statement.getColumnTypes().get(column).objectToSql(defaultValue, database)); } // TODO: Change this - vertica supports both auto incremental & identity fields. if (isAutoIncrementColumn) { // TODO: check if database supports auto increment on non primary key column if (database.supportsAutoIncrement()) { String autoIncrementClause = database.getAutoIncrementClause( autoIncrementConstraint.getStartWith(), autoIncrementConstraint.getIncrementBy()); if (!"".equals(autoIncrementClause)) { sql.append(" ").append(autoIncrementClause); } if (database instanceof PostgresDatabase && autoIncrementConstraint.getStartWith() != null) { String sequenceName = statement.getTableName() + "_" + column + "_seq"; // additionalSql.add(new UnparsedSql("alter sequence // "+database.escapeSequenceName(statement.getCatalogName(), statement.getSchemaName(), // sequenceName)+" start with "+autoIncrementConstraint.getStartWith(), new // Sequence().setName(sequenceName).setSchema(statement.getCatalogName(), // statement.getSchemaName()))); } } else { LogFactory.getLogger() .warning( database.getShortName() + " does not support autoincrement columns as request for " + (database.escapeTableName( statement.getCatalogName(), statement.getSchemaName(), statement.getTableName()))); } } if (isPrimaryKeyColumn) { String pkName = StringUtils.trimToNull(statement.getPrimaryKeyConstraint().getConstraintName()); if (pkName != null) { sql.append(" CONSTRAINT "); sql.append(database.escapeConstraintName(pkName)); } sql.append(" PRIMARY KEY "); } if (statement.getNotNullColumns().contains(column)) { sql.append(" NOT NULL"); } if (statement.getColumnEncoding(column) != null) { sql.append(" ENCODING ").append(statement.getColumnEncoding(column)); } if (statement.getColumnAccessrank(column) != null) sql.append(" ACCESSRANK ").append(statement.getColumnAccessrank(column)); if (columnIterator.hasNext()) { sql.append(", "); } } sql.append(" )"); sql.append(" ON COMMIT "); if (statement.getIsPreserve()) sql.append(" PRESERVE "); else sql.append(" DELETE"); sql.append(" ROWS "); if (statement.getOrderby() != null) sql.append(" ORDER BY ").append(statement.getOrderby()); if (statement.getSegmentation() != null) { Segmentation seg = statement.getSegmentation(); if (seg.getUnsegmented() == true) { sql.append(" UNSEGMENTED "); } else { sql.append(" SEGMENTED BY "); sql.append(seg.getExpression()); } if (seg.getAllNodes()) { sql.append(" ALL NODES "); } else { sql.append(" NODES ").append(seg.getNodes()); if (seg.getOffset() != null) sql.append(" OFFSET ").append(seg.getOffset().toString()); } } if (statement.getKsafe() != null) sql.append(" KSAFE ").append(statement.getKsafe()); if (statement.getPartitionby() != null) sql.append(" PARTITION BY ").append(statement.getPartitionby()); System.out.println(sql.toString()); return new Sql[] {new UnparsedSql(sql.toString())}; }
/** * This method will actually execute each of the changes in the list against the specified * database. * * @return should change set be marked as ran */ public ExecType execute( DatabaseChangeLog databaseChangeLog, ChangeExecListener listener, Database database) throws MigrationFailedException { if (validationFailed) { return ExecType.MARK_RAN; } long startTime = new Date().getTime(); ExecType execType = null; boolean skipChange = false; Executor executor = ExecutorService.getInstance().getExecutor(database); try { // set object quoting strategy database.setObjectQuotingStrategy(objectQuotingStrategy); // set auto-commit based on runInTransaction if database supports DDL in transactions if (database.supportsDDLInTransaction()) { database.setAutoCommit(!runInTransaction); } executor.comment("Changeset " + toString(false)); if (StringUtils.trimToNull(getComments()) != null) { String comments = getComments(); String[] lines = comments.split("\\n"); for (int i = 0; i < lines.length; i++) { if (i > 0) { lines[i] = database.getLineComment() + " " + lines[i]; } } executor.comment(StringUtils.join(Arrays.asList(lines), "\n")); } try { if (preconditions != null) { preconditions.check(database, databaseChangeLog, this); } } catch (PreconditionFailedException e) { if (listener != null) { listener.preconditionFailed(e, preconditions.getOnFail()); } StringBuffer message = new StringBuffer(); message.append(StreamUtil.getLineSeparator()); for (FailedPrecondition invalid : e.getFailedPreconditions()) { message.append(" ").append(invalid.toString()); message.append(StreamUtil.getLineSeparator()); } if (preconditions.getOnFail().equals(PreconditionContainer.FailOption.HALT)) { throw new MigrationFailedException(this, message.toString(), e); } else if (preconditions.getOnFail().equals(PreconditionContainer.FailOption.CONTINUE)) { skipChange = true; execType = ExecType.SKIPPED; LogFactory.getLogger() .info( "Continuing past: " + toString() + " despite precondition failure due to onFail='CONTINUE': " + message); } else if (preconditions.getOnFail().equals(PreconditionContainer.FailOption.MARK_RAN)) { execType = ExecType.MARK_RAN; skipChange = true; log.info( "Marking ChangeSet: " + toString() + " ran despite precondition failure due to onFail='MARK_RAN': " + message); } else if (preconditions.getOnFail().equals(PreconditionContainer.FailOption.WARN)) { execType = null; // already warned } else { throw new UnexpectedLiquibaseException( "Unexpected precondition onFail attribute: " + preconditions.getOnFail(), e); } } catch (PreconditionErrorException e) { if (listener != null) { listener.preconditionErrored(e, preconditions.getOnError()); } StringBuffer message = new StringBuffer(); message.append(StreamUtil.getLineSeparator()); for (ErrorPrecondition invalid : e.getErrorPreconditions()) { message.append(" ").append(invalid.toString()); message.append(StreamUtil.getLineSeparator()); } if (preconditions.getOnError().equals(PreconditionContainer.ErrorOption.HALT)) { throw new MigrationFailedException(this, message.toString(), e); } else if (preconditions.getOnError().equals(PreconditionContainer.ErrorOption.CONTINUE)) { skipChange = true; execType = ExecType.SKIPPED; } else if (preconditions.getOnError().equals(PreconditionContainer.ErrorOption.MARK_RAN)) { execType = ExecType.MARK_RAN; skipChange = true; log.info( "Marking ChangeSet: " + toString() + " ran despite precondition error: " + message); } else if (preconditions.getOnError().equals(PreconditionContainer.ErrorOption.WARN)) { execType = null; // already logged } else { throw new UnexpectedLiquibaseException( "Unexpected precondition onError attribute: " + preconditions.getOnError(), e); } database.rollback(); } finally { database.rollback(); } if (!skipChange) { for (Change change : changes) { try { change.finishInitialization(); } catch (SetupException se) { throw new MigrationFailedException(this, se); } } log.debug("Reading ChangeSet: " + toString()); for (Change change : getChanges()) { if ((!(change instanceof DbmsTargetedChange)) || DatabaseList.definitionMatches( ((DbmsTargetedChange) change).getDbms(), database, true)) { if (listener != null) { listener.willRun(change, this, changeLog, database); } if (change.generateStatementsVolatile(database)) { executor.comment( "WARNING The following SQL is possibly incorrect, invalid, and/or may change on each run:"); } database.executeStatements(change, databaseChangeLog, sqlVisitors); log.info(change.getConfirmationMessage()); if (listener != null) { listener.ran(change, this, changeLog, database); } } else { log.debug( "Change " + change.getSerializedObjectName() + " not included for database " + database.getShortName()); } } if (runInTransaction) { database.commit(); } log.info( "ChangeSet " + toString(false) + " ran successfully in " + (new Date().getTime() - startTime + "ms")); if (execType == null) { execType = ExecType.EXECUTED; } } else { log.debug("Skipping ChangeSet: " + toString()); } } catch (Exception e) { try { database.rollback(); } catch (Exception e1) { throw new MigrationFailedException(this, e); } if (getFailOnError() != null && !getFailOnError()) { log.info( "Change set " + toString(false) + " failed, but failOnError was false. Error: " + e.getMessage()); log.debug("Failure Stacktrace", e); execType = ExecType.FAILED; } else { // just log the message, dont log the stacktrace by appending exception. Its logged anyway // to stdout log.severe("Change Set " + toString(false) + " failed. Error: " + e.getMessage()); if (e instanceof MigrationFailedException) { throw ((MigrationFailedException) e); } else { throw new MigrationFailedException(this, e); } } } finally { // restore auto-commit to false if this ChangeSet was not run in a transaction, // but only if the database supports DDL in transactions if (!runInTransaction && database.supportsDDLInTransaction()) { try { database.setAutoCommit(false); } catch (DatabaseException e) { throw new MigrationFailedException(this, "Could not resetInternalState autocommit", e); } } } return execType; }
/** Drops all objects owned by the connected user. */ @Override public void dropDatabaseObjects(final CatalogAndSchema schemaToDrop) throws LiquibaseException { ObjectQuotingStrategy currentStrategy = this.getObjectQuotingStrategy(); this.setObjectQuotingStrategy(ObjectQuotingStrategy.QUOTE_ALL_OBJECTS); try { DatabaseSnapshot snapshot; try { final SnapshotControl snapshotControl = new SnapshotControl(this); final Set<Class<? extends DatabaseObject>> typesToInclude = snapshotControl.getTypesToInclude(); // We do not need to remove indexes and primary/unique keys explicitly. They should be // removed // as part of tables. typesToInclude.remove(Index.class); typesToInclude.remove(PrimaryKey.class); typesToInclude.remove(UniqueConstraint.class); if (supportsForeignKeyDisable()) { // We do not remove ForeignKey because they will be disabled and removed as parts of // tables. typesToInclude.remove(ForeignKey.class); } final long createSnapshotStarted = System.currentTimeMillis(); snapshot = SnapshotGeneratorFactory.getInstance() .createSnapshot(schemaToDrop, this, snapshotControl); LogFactory.getLogger() .debug( String.format( "Database snapshot generated in %d ms. Snapshot includes: %s", System.currentTimeMillis() - createSnapshotStarted, typesToInclude)); } catch (LiquibaseException e) { throw new UnexpectedLiquibaseException(e); } final long changeSetStarted = System.currentTimeMillis(); DiffResult diffResult = DiffGeneratorFactory.getInstance() .compare( new EmptyDatabaseSnapshot(this), snapshot, new CompareControl(snapshot.getSnapshotControl().getTypesToInclude())); List<ChangeSet> changeSets = new DiffToChangeLog( diffResult, new DiffOutputControl(true, true, false).addIncludedSchema(schemaToDrop)) .generateChangeSets(); LogFactory.getLogger() .debug( String.format( "ChangeSet to Remove Database Objects generated in %d ms.", System.currentTimeMillis() - changeSetStarted)); boolean previousAutoCommit = this.getAutoCommitMode(); this.commit(); // clear out currently executed statements this.setAutoCommit(false); // some DDL doesn't work in autocommit mode final boolean reEnableFK = supportsForeignKeyDisable() && disableForeignKeyChecks(); try { for (ChangeSet changeSet : changeSets) { changeSet.setFailOnError(false); for (Change change : changeSet.getChanges()) { if (change instanceof DropTableChange) { ((DropTableChange) change).setCascadeConstraints(true); } SqlStatement[] sqlStatements = change.generateStatements(this); for (SqlStatement statement : sqlStatements) { ExecutorService.getInstance().getExecutor(this).execute(statement); } } this.commit(); } } finally { if (reEnableFK) { enableForeignKeyChecks(); } } ChangeLogHistoryServiceFactory.getInstance().getChangeLogService(this).destroy(); LockServiceFactory.getInstance().getLockService(this).destroy(); this.setAutoCommit(previousAutoCommit); } finally { this.setObjectQuotingStrategy(currentStrategy); this.commit(); } }
@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(); } }
public static Object parseValue(Database database, Object val, DataType type) { if (!(val instanceof String)) { return val; } int typeId = Integer.MIN_VALUE; if (type.getDataTypeId() != null) { typeId = type.getDataTypeId(); } String typeName = type.getTypeName(); LiquibaseDataType liquibaseDataType = DataTypeFactory.getInstance().from(type, database); String stringVal = (String) val; if (stringVal.isEmpty()) { if (liquibaseDataType instanceof CharType) { return ""; } else { return null; } } if (database instanceof OracleDatabase && !stringVal.startsWith("'") && !stringVal.endsWith("'")) { // oracle returns functions without quotes Object maybeDate = null; if (liquibaseDataType instanceof DateType || typeId == Types.DATE) { if (stringVal.endsWith("'HH24:MI:SS')")) { maybeDate = DataTypeFactory.getInstance() .fromDescription("time", database) .sqlToObject(stringVal, database); } else { maybeDate = DataTypeFactory.getInstance() .fromDescription("date", database) .sqlToObject(stringVal, database); } } else if (liquibaseDataType instanceof DateTimeType || typeId == Types.TIMESTAMP) { maybeDate = DataTypeFactory.getInstance() .fromDescription("datetime", database) .sqlToObject(stringVal, database); } else { return new DatabaseFunction(stringVal); } if (maybeDate != null) { if (maybeDate instanceof java.util.Date) { return maybeDate; } else { return new DatabaseFunction(stringVal); } } } if (stringVal.startsWith("'") && stringVal.endsWith("'")) { stringVal = stringVal.substring(1, stringVal.length() - 1); } else if (stringVal.startsWith("((") && stringVal.endsWith("))")) { stringVal = stringVal.substring(2, stringVal.length() - 2); } else if (stringVal.startsWith("('") && stringVal.endsWith("')")) { stringVal = stringVal.substring(2, stringVal.length() - 2); } else if (stringVal.startsWith("(") && stringVal.endsWith(")")) { return new DatabaseFunction(stringVal.substring(1, stringVal.length() - 1)); } Scanner scanner = new Scanner(stringVal.trim()); if (typeId == Types.ARRAY) { return new DatabaseFunction(stringVal); } else if ((liquibaseDataType instanceof BigIntType || typeId == Types.BIGINT)) { if (scanner.hasNextBigInteger()) { return scanner.nextBigInteger(); } else { return new DatabaseFunction(stringVal); } } else if (typeId == Types.BINARY) { return new DatabaseFunction(stringVal.trim()); } else if (typeId == Types.BIT) { if (stringVal.startsWith("b'")) { // mysql returns boolean values as b'0' and b'1' stringVal = stringVal.replaceFirst("b'", "").replaceFirst("'$", ""); } stringVal = stringVal.trim(); if (scanner.hasNextBoolean()) { return scanner.nextBoolean(); } else { return new Integer(stringVal); } } else if (liquibaseDataType instanceof BlobType || typeId == Types.BLOB) { return new DatabaseFunction(stringVal); } else if ((liquibaseDataType instanceof BooleanType || typeId == Types.BOOLEAN)) { if (scanner.hasNextBoolean()) { return scanner.nextBoolean(); } else { return new DatabaseFunction(stringVal); } } else if (liquibaseDataType instanceof CharType || typeId == Types.CHAR) { return stringVal; } else if (liquibaseDataType instanceof ClobType || typeId == Types.CLOB) { return stringVal; } else if (typeId == Types.DATALINK) { return new DatabaseFunction(stringVal); } else if (liquibaseDataType instanceof DateType || typeId == Types.DATE) { if (typeName.equalsIgnoreCase("year")) { return stringVal.trim(); } return DataTypeFactory.getInstance() .fromDescription("date", database) .sqlToObject(stringVal, database); } else if ((liquibaseDataType instanceof DecimalType || typeId == Types.DECIMAL)) { if (scanner.hasNextBigDecimal()) { return scanner.nextBigDecimal(); } else { return new DatabaseFunction(stringVal); } } else if (typeId == Types.DISTINCT) { return new DatabaseFunction(stringVal); } else if ((liquibaseDataType instanceof DoubleType || typeId == Types.DOUBLE)) { if (scanner.hasNextDouble()) { return scanner.nextDouble(); } else { return new DatabaseFunction(stringVal); } } else if ((liquibaseDataType instanceof FloatType || typeId == Types.FLOAT)) { if (scanner.hasNextFloat()) { return scanner.nextFloat(); } else { return new DatabaseFunction(stringVal); } } else if ((liquibaseDataType instanceof IntType || typeId == Types.INTEGER)) { if (scanner.hasNextInt()) { return scanner.nextInt(); } else { return new DatabaseFunction(stringVal); } } else if (typeId == Types.JAVA_OBJECT) { return new DatabaseFunction(stringVal); } else if (typeId == Types.LONGNVARCHAR) { return stringVal; } else if (typeId == Types.LONGVARBINARY) { return new DatabaseFunction(stringVal); } else if (typeId == Types.LONGVARCHAR) { return stringVal; } else if (liquibaseDataType instanceof NCharType || typeId == Types.NCHAR) { return stringVal; } else if (typeId == Types.NCLOB) { return stringVal; } else if (typeId == Types.NULL) { return null; } else if ((liquibaseDataType instanceof NumberType || typeId == Types.NUMERIC)) { if (scanner.hasNextBigDecimal()) { return scanner.nextBigDecimal(); } else { return new DatabaseFunction(stringVal); } } else if (liquibaseDataType instanceof NVarcharType || typeId == Types.NVARCHAR) { return stringVal; } else if (typeId == Types.OTHER) { if (database instanceof DB2Database && typeName.equalsIgnoreCase("DECFLOAT")) { return new BigDecimal(stringVal); } return new DatabaseFunction(stringVal); } else if (typeId == Types.REAL) { return new BigDecimal(stringVal.trim()); } else if (typeId == Types.REF) { return new DatabaseFunction(stringVal); } else if (typeId == Types.ROWID) { return new DatabaseFunction(stringVal); } else if ((liquibaseDataType instanceof SmallIntType || typeId == Types.SMALLINT)) { if (scanner.hasNextInt()) { return scanner.nextInt(); } else { return new DatabaseFunction(stringVal); } } else if (typeId == Types.SQLXML) { return new DatabaseFunction(stringVal); } else if (typeId == Types.STRUCT) { return new DatabaseFunction(stringVal); } else if (liquibaseDataType instanceof TimeType || typeId == Types.TIME) { return DataTypeFactory.getInstance() .fromDescription("time", database) .sqlToObject(stringVal, database); } else if (liquibaseDataType instanceof DateTimeType || liquibaseDataType instanceof TimestampType || typeId == Types.TIMESTAMP) { return DataTypeFactory.getInstance() .fromDescription("datetime", database) .sqlToObject(stringVal, database); } else if ((liquibaseDataType instanceof TinyIntType || typeId == Types.TINYINT)) { if (scanner.hasNextInt()) { return scanner.nextInt(); } else { return new DatabaseFunction(stringVal); } } else if (typeId == Types.VARBINARY) { return new DatabaseFunction(stringVal); } else if (liquibaseDataType instanceof VarcharType || typeId == Types.VARCHAR) { return stringVal; } else if (database instanceof MySQLDatabase && typeName.toLowerCase().startsWith("enum")) { return stringVal; } else { LogFactory.getLogger() .info( "Unknown default value: value '" + stringVal + "' type " + typeName + " (" + type + "), assuming it is a function"); return new DatabaseFunction(stringVal); } }