package liquibase.parser.core.formattedsql; import liquibase.change.core.EmptyChange; import liquibase.change.core.RawSQLChange; import liquibase.changelog.ChangeLogParameters; import liquibase.changelog.ChangeSet; import liquibase.changelog.DatabaseChangeLog; import liquibase.exception.ChangeLogParseException; import liquibase.exception.UnsupportedChangeException; import liquibase.parser.ChangeLogParser; import liquibase.resource.ResourceAccessor; import liquibase.util.StringUtils; import java.io.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; public class FormattedSqlChangeLogParser implements ChangeLogParser { Logger logger = Logger.getLogger(getClass().getName()); public boolean supports(String changeLogFile, ResourceAccessor resourceAccessor) { BufferedReader reader = null; try { if (changeLogFile.endsWith(".sql")) { reader = new BufferedReader(new InputStreamReader(openChangeLogFile(changeLogFile, resourceAccessor))); return reader.readLine().matches("\\-\\-\\s*liquibase formatted.*"); } else { return false; } } catch (IOException e) { logger.log(Level.FINE,"Exception reading " + changeLogFile, e); return false; } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { logger.log(Level.FINE,"Exception closing " + changeLogFile, e); } } } } public int getPriority() { return PRIORITY_DEFAULT + 5; } public DatabaseChangeLog parse(String physicalChangeLogLocation, ChangeLogParameters changeLogParameters, ResourceAccessor resourceAccessor) throws ChangeLogParseException { DatabaseChangeLog changeLog = new DatabaseChangeLog(); changeLog.setPhysicalFilePath(physicalChangeLogLocation); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(openChangeLogFile(physicalChangeLogLocation, resourceAccessor))); StringBuffer currentSql = new StringBuffer(); StringBuffer currentRollbackSql = new StringBuffer(); ChangeSet changeSet = null; RawSQLChange change = null; Pattern changeSetPattern = Pattern.compile("\\-\\-[\\s]*changeset (\\w+):(\\w+).*", Pattern.CASE_INSENSITIVE); Pattern rollbackPattern = Pattern.compile("\\s*\\-\\-[\\s]*rollback (.*)", Pattern.CASE_INSENSITIVE); Pattern stripCommentsPattern = Pattern.compile(".*stripComments:(\\w+).*", Pattern.CASE_INSENSITIVE); Pattern splitStatementsPattern = Pattern.compile(".*splitStatements:(\\w+).*", Pattern.CASE_INSENSITIVE); Pattern endDelimiterPattern = Pattern.compile(".*endDelimiter:(\\w+).*", Pattern.CASE_INSENSITIVE); Pattern runOnChangePattern = Pattern.compile(".*runOnChange:(\\w+).*", Pattern.CASE_INSENSITIVE); Pattern runAlwaysPattern = Pattern.compile(".*runAlways:(\\w+).*", Pattern.CASE_INSENSITIVE); Pattern contextPattern = Pattern.compile(".*context:(\\w+).*", Pattern.CASE_INSENSITIVE); Pattern runInTransactionPattern = Pattern.compile(".*runInTransaction:(\\w+).*", Pattern.CASE_INSENSITIVE); Pattern dbmsPattern = Pattern.compile(".*dbms:(\\w+).*", Pattern.CASE_INSENSITIVE); Pattern failOnErrorPattern = Pattern.compile(".*failOnError:(\\w+).*", Pattern.CASE_INSENSITIVE); String line; while ((line = reader.readLine()) != null) { Matcher changeSetPatternMatcher = changeSetPattern.matcher(line); if (changeSetPatternMatcher.matches()) { String finalCurrentSql = changeLogParameters.expandExpressions(StringUtils.trimToNull(currentSql.toString())); if (changeSet != null) { if (finalCurrentSql == null) { throw new ChangeLogParseException("No SQL for changeset " + changeSet.toString(false)); } change.setSql(finalCurrentSql); if (StringUtils.trimToNull(currentRollbackSql.toString()) != null) { try { if (currentRollbackSql.toString().trim().toLowerCase().matches("^not required.*")) { changeSet.addRollbackChange(new EmptyChange()); } else { RawSQLChange rollbackChange = new RawSQLChange(); rollbackChange.setSql(changeLogParameters.expandExpressions(currentRollbackSql.toString())); changeSet.addRollbackChange(rollbackChange); } } catch (UnsupportedChangeException e) { throw new RuntimeException(e); } } } Matcher stripCommentsPatternMatcher = stripCommentsPattern.matcher(line); Matcher splitStatementsPatternMatcher = splitStatementsPattern.matcher(line); Matcher endDelimiterPatternMatcher = endDelimiterPattern.matcher(line); Matcher runOnChangePatternMatcher = runOnChangePattern.matcher(line); Matcher runAlwaysPatternMatcher = runAlwaysPattern.matcher(line); Matcher contextPatternMatcher = contextPattern.matcher(line); Matcher runInTransactionPatternMatcher = runInTransactionPattern.matcher(line); Matcher dbmsPatternMatcher = dbmsPattern.matcher(line); Matcher failOnErrorPatternMatcher = failOnErrorPattern.matcher(line); boolean stripComments = parseBoolean(stripCommentsPatternMatcher, changeSet, true); boolean splitStatements = parseBoolean(splitStatementsPatternMatcher, changeSet, true); boolean runOnChange = parseBoolean(runOnChangePatternMatcher, changeSet, false); boolean runAlways = parseBoolean(runAlwaysPatternMatcher, changeSet, false); boolean runInTransaction = parseBoolean(runInTransactionPatternMatcher, changeSet, true); boolean failOnError = parseBoolean(failOnErrorPatternMatcher, changeSet, true); String endDelimiter = parseString(endDelimiterPatternMatcher); String context = parseString(contextPatternMatcher); String dbms = parseString(dbmsPatternMatcher); changeSet = new ChangeSet(changeSetPatternMatcher.group(2), changeSetPatternMatcher.group(1), runAlways, runOnChange, physicalChangeLogLocation, context, dbms, runInTransaction); changeSet.setFailOnError(failOnError); changeLog.addChangeSet(changeSet); change = new RawSQLChange(); change.setSql(finalCurrentSql); change.setResourceAccessor(resourceAccessor); change.setSplitStatements(splitStatements); change.setStripComments(stripComments); change.setEndDelimiter(endDelimiter); changeSet.addChange(change); currentSql = new StringBuffer(); currentRollbackSql = new StringBuffer(); } else { if (changeSet != null) { Matcher rollbackMatcher = rollbackPattern.matcher(line); if (rollbackMatcher.matches()) { if (rollbackMatcher.groupCount() == 1) { currentRollbackSql.append(rollbackMatcher.group(1)).append("\n"); } } else { currentSql.append(line).append("\n"); } } } } if (changeSet != null) { change.setSql(changeLogParameters.expandExpressions(StringUtils.trimToNull(currentSql.toString()))); if (StringUtils.trimToNull(currentRollbackSql.toString()) != null) { try { if (currentRollbackSql.toString().trim().toLowerCase().matches("^not required.*")) { changeSet.addRollbackChange(new EmptyChange()); } else { RawSQLChange rollbackChange = new RawSQLChange(); rollbackChange.setSql(changeLogParameters.expandExpressions(currentRollbackSql.toString())); changeSet.addRollbackChange(rollbackChange); } } catch (UnsupportedChangeException e) { throw new RuntimeException(e); } } } } catch (IOException e) { throw new ChangeLogParseException(e); } finally { if (reader != null) { try { reader.close(); } catch (IOException ignore) { } } } return changeLog; } private String parseString(Matcher matcher) { String endDelimiter = null; if (matcher.matches()) { endDelimiter = matcher.group(1); } return endDelimiter; } private boolean parseBoolean(Matcher matcher, ChangeSet changeSet, boolean defaultValue) throws ChangeLogParseException { boolean stripComments = defaultValue; if (matcher.matches()) { try { stripComments = Boolean.parseBoolean(matcher.group(1)); } catch (Exception e) { throw new ChangeLogParseException("Cannot parse "+changeSet+" "+matcher.toString().replaceAll("\\.*","")+" as a boolean"); } } return stripComments; } protected InputStream openChangeLogFile(String physicalChangeLogLocation, ResourceAccessor resourceAccessor) throws IOException { return resourceAccessor.getResourceAsStream(physicalChangeLogLocation); } }