/** Tags the database changelog with the given string. */ @Override public void tag(String tagString) throws DatabaseException { Executor executor = ExecutorService.getInstance().getExecutor(this); try { int totalRows = ExecutorService.getInstance() .getExecutor(this) .queryForInt(new SelectFromDatabaseChangeLogStatement("COUNT(*)")); if (totalRows == 0) { ChangeSet emptyChangeSet = new ChangeSet( String.valueOf(new Date().getTime()), "liquibase", false, false, "liquibase-internal", null, null); this.markChangeSetExecStatus(emptyChangeSet, ChangeSet.ExecType.EXECUTED); } // Timestamp lastExecutedDate = (Timestamp) // this.getExecutor().queryForObject(createChangeToTagSQL(), // Timestamp.class); executor.execute(new TagDatabaseStatement(tagString)); this.commit(); getRanChangeSetList().get(getRanChangeSetList().size() - 1).setTag(tagString); } catch (Exception e) { throw new DatabaseException(e); } }
@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) {; } } }
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(); } }
/** * Generates the SQL statements required to run the change. * * @param database databasethe target {@link liquibase.database.Database} associated to this * change's statements * @return an array of {@link String}s with the statements */ public SqlStatement[] generateStatements(Database database) { final InsertStatement insertDefinition = new InsertStatement(database.getDefaultSchemaName(), "krim_attr_defn_t"); final SqlStatement getId = new RuntimeStatement() { public Sql[] generate(Database database) { return new Sql[] { new UnparsedSql("insert into krim_attr_defn_id_s values(null)"), new UnparsedSql("select max(id) from krim_attr_defn_id_s") }; } }; try { final BigInteger id = (BigInteger) ExecutorService.getInstance() .getExecutor(database) .queryForObject(getId, BigInteger.class); insertDefinition.addColumnValue("KIM_ATTR_DEFN_ID", id); insertDefinition.addColumnValue("nmspc_cd", getNamespace()); insertDefinition.addColumnValue("NM", getName()); insertDefinition.addColumnValue("LBL", getLabel()); insertDefinition.addColumnValue("actv_ind", getActive()); insertDefinition.addColumnValue("CMPNT_NM", getComponent()); insertDefinition.addColumnValue("ver_nbr", 1); insertDefinition.addColumnValue("obj_id", "sys_guid()"); } catch (Exception e) { throw new RuntimeException(e); } return new SqlStatement[] {insertDefinition}; }
public DatabaseChangeLogLock[] listLocks() throws DatabaseException { try { if (!database.hasDatabaseChangeLogLockTable()) { return new DatabaseChangeLogLock[0]; } List<DatabaseChangeLogLock> allLocks = new ArrayList<DatabaseChangeLogLock>(); SqlStatement sqlStatement = new SelectFromDatabaseChangeLogLockStatement("ID", "LOCKED", "LOCKGRANTED", "LOCKEDBY"); List<Map> rows = ExecutorService.getInstance().getExecutor(database).queryForList(sqlStatement); for (Map columnMap : rows) { Object lockedValue = columnMap.get("LOCKED"); Boolean locked; if (lockedValue instanceof Number) { locked = ((Number) lockedValue).intValue() == 1; } else { locked = (Boolean) lockedValue; } if (locked != null && locked) { allLocks.add( new DatabaseChangeLogLock( ((Number) columnMap.get("ID")).intValue(), (Date) columnMap.get("LOCKGRANTED"), (String) columnMap.get("LOCKEDBY"))); } } return allLocks.toArray(new DatabaseChangeLogLock[allLocks.size()]); } finally { database .rollback(); // Rollback to make sure any database explicitly imposed lock from the above // SELECT statement gets released. CORE-1219 incident. } }
public boolean isDatabaseChangeLogLockTableInitialized(final boolean tableJustCreated) throws DatabaseException { if (!isDatabaseChangeLogLockTableInitialized) { Executor executor = ExecutorService.getInstance().getExecutor(database); try { isDatabaseChangeLogLockTableInitialized = executor.queryForInt( new RawSqlStatement( "select count(*) from " + database.escapeTableName( database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName()))) > 0; } catch (LiquibaseException e) { if (executor.updatesDatabase()) { throw new UnexpectedLiquibaseException(e); } else { // probably didn't actually create the table yet. isDatabaseChangeLogLockTableInitialized = !tableJustCreated; } } } return isDatabaseChangeLogLockTableInitialized; }
@Override public DatabaseChangeLogLock[] listLocks() throws LockException { try { if (!this.hasDatabaseChangeLogLockTable()) { return new DatabaseChangeLogLock[0]; } List<DatabaseChangeLogLock> allLocks = new ArrayList<DatabaseChangeLogLock>(); SqlStatement sqlStatement = new SelectFromDatabaseChangeLogLockStatement("ID", "LOCKED", "LOCKGRANTED", "LOCKEDBY"); List<Map<String, ?>> rows = ExecutorService.getInstance().getExecutor(database).queryForList(sqlStatement); for (Map columnMap : rows) { Object lockedValue = columnMap.get("LOCKED"); Boolean locked; if (lockedValue instanceof Number) { locked = ((Number) lockedValue).intValue() == 1; } else { locked = (Boolean) lockedValue; } if (locked != null && locked) { allLocks.add( new DatabaseChangeLogLock( ((Number) columnMap.get("ID")).intValue(), (Date) columnMap.get("LOCKGRANTED"), (String) columnMap.get("LOCKEDBY"))); } } return allLocks.toArray(new DatabaseChangeLogLock[allLocks.size()]); } catch (Exception e) { throw new LockException(e); } }
@Test public void accepts() throws DatabaseException { ArrayList<RanChangeSet> ranChanges = new ArrayList<RanChangeSet>(); ranChanges.add( new RanChangeSet( "path/changelog", "1", "testAuthor", CheckSum.parse("12345"), new Date(), null, null)); ranChanges.add( new RanChangeSet( "path/changelog", "2", "testAuthor", CheckSum.parse("12345"), new Date(), null, null)); Database database = createMock(Database.class); expect(database.getRanChangeSetList()).andReturn(ranChanges); expect(database.getDatabaseChangeLogTableName()).andReturn("DATABASECHANGELOG").anyTimes(); expect(database.getDefaultSchemaName()).andReturn(null).anyTimes(); Executor template = createMock(Executor.class); expect(template.update(isA(UpdateStatement.class))).andReturn(1).anyTimes(); // template.comment("Lock Database"); // expectLastCall(); replay(database); replay(template); ExecutorService.getInstance().setExecutor(database, template); ShouldRunChangeSetFilter filter = new ShouldRunChangeSetFilter(database); // everything same assertFalse( filter.accepts( new ChangeSet("1", "testAuthor", false, false, "path/changelog", null, null))); // alwaysRun assertTrue( filter.accepts( new ChangeSet("1", "testAuthor", true, false, "path/changelog", null, null))); // run on change assertTrue( filter.accepts( new ChangeSet("1", "testAuthor", false, true, "path/changelog", null, null))); // different id assertTrue( filter.accepts( new ChangeSet("3", "testAuthor", false, false, "path/changelog", null, null))); // different author assertTrue( filter.accepts( new ChangeSet("1", "otherAuthor", false, false, "path/changelog", null, null))); // different path assertTrue( filter.accepts( new ChangeSet("1", "testAuthor", false, false, "other/changelog", null, null))); }
@Override public boolean doesTagExist(String tag) throws DatabaseException { int count = ExecutorService.getInstance() .getExecutor(this) .queryForInt( new SelectFromDatabaseChangeLogStatement( new SelectFromDatabaseChangeLogStatement.ByTag("tag"), "COUNT(*)")); return count > 0; }
/** * After the change set has been ran against the database this method will update the change log * table with the information. */ @Override public void markChangeSetExecStatus(ChangeSet changeSet, ChangeSet.ExecType execType) throws DatabaseException { ExecutorService.getInstance() .getExecutor(this) .execute(new MarkChangeSetRanStatement(changeSet, execType)); commit(); getRanChangeSetList().add(new RanChangeSet(changeSet, execType)); }
@Override public void removeRanStatus(ChangeSet changeSet) throws DatabaseException { ExecutorService.getInstance() .getExecutor(this) .execute(new RemoveChangeSetRanStatusStatement(changeSet)); commit(); getRanChangeSetList().remove(new RanChangeSet(changeSet)); }
/** 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); } }
@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 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; }
/** * 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; }
@Test public void testUpdateSQLNoAlterSqlDryMode() { ExecutorService.getInstance() .setExecutor(database, new LoggingExecutor(null, new StringWriter(), database)); System.setProperty(Configuration.NO_ALTER_SQL_DRY_MODE, "true"); SqlStatement[] statements = c.generateStatements(database); Assert.assertEquals(1, statements.length); Assert.assertEquals(CommentStatement.class, statements[0].getClass()); Assert.assertEquals( "pt-online-schema-change --alter=\"DROP COLUMN col_test\" " + "--host=localhost --port=3306 --user=user --password=*** --execute D=testdb,t=person", ((CommentStatement) statements[0]).getText()); }
@Override public String getViewDefinition(String schemaName, String viewName) throws DatabaseException { if (schemaName == null) { schemaName = convertRequestedSchemaToSchema(null); } String definition = (String) ExecutorService.getInstance() .getExecutor(this) .queryForObject(new GetViewDefinitionStatement(schemaName, viewName), String.class); if (definition == null) { return null; } return CREATE_VIEW_AS_PATTERN.matcher(definition).replaceFirst(""); }
@Override public int getNextChangeSetSequenceValue() throws LiquibaseException { if (lastChangeSetSequenceValue == null) { if (getConnection() == null) { lastChangeSetSequenceValue = 0; } else { lastChangeSetSequenceValue = ExecutorService.getInstance() .getExecutor(this) .queryForInt(new GetNextChangeSetSequenceValueStatement()); } } return ++lastChangeSetSequenceValue; }
@Test public void testUpdateSQL() { ExecutorService.getInstance() .setExecutor(database, new LoggingExecutor(null, new StringWriter(), database)); SqlStatement[] statements = c.generateStatements(database); Assert.assertEquals(3, statements.length); Assert.assertEquals(CommentStatement.class, statements[0].getClass()); Assert.assertEquals( "pt-online-schema-change --alter=\"DROP COLUMN col_test\" " + "--host=localhost --port=3306 --user=user --password=*** --execute D=testdb,t=person", ((CommentStatement) statements[0]).getText()); Assert.assertEquals(CommentStatement.class, statements[1].getClass()); Assert.assertEquals(DropColumnStatement.class, statements[2].getClass()); }
@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; }
/** * 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; } }
@Override public String getViewDefinition(CatalogAndSchema schema, final String viewName) throws DatabaseException { schema = schema.customize(this); String definition = (String) ExecutorService.getInstance() .getExecutor(this) .queryForObject( new GetViewDefinitionStatement( schema.getCatalogName(), schema.getSchemaName(), viewName), String.class); if (definition == null) { return null; } return CREATE_VIEW_AS_PATTERN.matcher(definition).replaceFirst(""); }
@Override public void setConnection(final DatabaseConnection connection) { super.setConnection(connection); try { /* * TODO Maybe there is a better place for this. * For each session this statement has to be executed, * to allow newlines in quoted strings */ ExecutorService.getInstance() .getExecutor(this) .execute(new RawSqlStatement("EXECUTE PROCEDURE IFX_ALLOW_NEWLINE('T');")); } catch (Exception e) { throw new UnexpectedLiquibaseException( "Could not allow newline characters in quoted strings with IFX_ALLOW_NEWLINE", e); } }
@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); }
@Override public String getViewDefinition(CatalogAndSchema schema, final String viewName) throws DatabaseException { schema = correctSchema(schema); List<Map> retList = ExecutorService.getInstance() .getExecutor(this) .queryForList( new GetViewDefinitionStatement( schema.getCatalogName(), schema.getSchemaName(), viewName)); // building the view definition from the multiple rows StringBuilder sb = new StringBuilder(); for (Map rowMap : retList) { String s = (String) rowMap.get("VIEWTEXT"); sb.append(s); } return CREATE_VIEW_AS_PATTERN.matcher(sb.toString()).replaceFirst(""); }
public Set<String> getUserDefinedTypes() { if (userDefinedTypes == null) { userDefinedTypes = new HashSet<String>(); if (getConnection() != null && !(getConnection() instanceof OfflineConnection)) { try { userDefinedTypes.addAll( ExecutorService.getInstance() .getExecutor(this) .queryForList( new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class)); } catch (DatabaseException e) { // ignore error } } } return userDefinedTypes; }
@Before public void setup() { c = new PerconaDropColumnChange(); c.setColumnName("col_test"); c.setTableName("person"); c.getColumns().clear(); DatabaseConnectionUtil.passwordForTests = "root"; database = new MySQLDatabase(); database.setLiquibaseSchemaName("testdb"); DatabaseConnection conn = new MockDatabaseConnection("jdbc:mysql://user@localhost:3306/testdb", "user@localhost"); database.setConnection(conn); ExecutorService.getInstance().setExecutor(database, new JdbcExecutor()); PTOnlineSchemaChangeStatement.available = true; System.setProperty(Configuration.FAIL_IF_NO_PT, "false"); System.setProperty(Configuration.NO_ALTER_SQL_DRY_MODE, "false"); }
@Override public void destroy() throws DatabaseException { try { if (SnapshotGeneratorFactory.getInstance() .has( new Table() .setName(database.getDatabaseChangeLogLockTableName()) .setSchema(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName()), database)) { ExecutorService.getInstance() .getExecutor(database) .execute( new DropTableStatement( database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName(), false)); } } catch (InvalidExampleException e) { throw new UnexpectedLiquibaseException(e); } }
@Override public boolean acquireLock() { if (hasChangeLogLock) { // We already have a lock return true; } Executor executor = ExecutorService.getInstance().getExecutor(database); try { database.rollback(); // Ensure table created and lock record inserted this.init(); } catch (DatabaseException de) { throw new IllegalStateException("Failed to retrieve lock", de); } try { log.debug("Trying to lock database"); executor.execute(new LockDatabaseChangeLogStatement()); log.debug("Successfully acquired database lock"); hasChangeLogLock = true; database.setCanCacheLiquibaseTableInfo(true); return true; } catch (DatabaseException de) { log.warn( "Lock didn't yet acquired. Will possibly retry to acquire lock. Details: " + de.getMessage()); if (log.isTraceEnabled()) { log.debug(de.getMessage(), de); } return false; } }