diff --git a/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/calculateChecksum.test.groovy b/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/calculateChecksum.test.groovy index f660927c23c..115127ba8e1 100644 --- a/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/calculateChecksum.test.groovy +++ b/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/calculateChecksum.test.groovy @@ -11,10 +11,17 @@ Short Description: Calculates and prints a checksum for the changeset Long Description: Calculates and prints a checksum for the changeset with the given id in the format filepath::id::author Required Args: changelogFile (String) The root changelog file - changesetIdentifier (String) Changeset ID identifier of form filepath::id::author url (String) The JDBC database connection URL OBFUSCATED Optional Args: + changesetAuthor (String) ChangeSet Author attribute + Default: null + changesetId (String) ChangeSet ID attribute + Default: null + changesetIdentifier (String) ChangeSet identifier of form filepath::id::author + Default: null + changesetPath (String) Changelog path in which the changeSet is included + Default: null defaultCatalogName (String) The default catalog name to use for the database connection Default: null defaultSchemaName (String) The default schema name to use for the database connection @@ -30,7 +37,7 @@ Optional Args: Default: null """ - run "Happy path", { + run "Happy path using changeSetIdentifier", { arguments = [ url : { it.altUrl }, username : { it.altUsername }, @@ -44,22 +51,29 @@ Optional Args: ] } - run "Run without changelogFile should throw an exception", { + run "Happy path using changeSetPath, ChangeSetId and ChangeSetPath", { arguments = [ - changesetIdentifier: "changelogs/h2/complete/rollback.tag.changelog.xml::1::nvoxland", + url : { it.altUrl }, + username : { it.altUsername }, + password : { it.altPassword }, + changesetPath : "changelogs/h2/complete/rollback.tag.changelog.xml", + changesetId : "1", + changesetAuthor : "nvoxland", + changelogFile : "changelogs/h2/complete/rollback.tag.changelog.xml" ] - expectedException = CommandValidationException.class - expectedExceptionMessage = 'Invalid argument \'changelogFile\': missing required argument' + expectedResults = [ + checksumResult : "9:10de8cd690aed1d88d837cbe555d1684" + ] } - run "Run without changesetIdentifier should throw an exception", { + run "Run without changelogFile should throw an exception", { arguments = [ - changelogFile : "changelogs/h2/complete/rollback.tag.changelog.xml" + changesetIdentifier: "changelogs/h2/complete/rollback.tag.changelog.xml::1::nvoxland", ] expectedException = CommandValidationException.class - expectedExceptionMessage = "Invalid argument \'changesetIdentifier\': missing required argument" + expectedExceptionMessage = 'Invalid argument \'changelogFile\': missing required argument' } run "Run without URL should throw an exception", { diff --git a/liquibase-standard/src/main/java/liquibase/Liquibase.java b/liquibase-standard/src/main/java/liquibase/Liquibase.java index 00fa7e326d4..aa9816f48fe 100644 --- a/liquibase-standard/src/main/java/liquibase/Liquibase.java +++ b/liquibase-standard/src/main/java/liquibase/Liquibase.java @@ -1230,23 +1230,25 @@ public void clearCheckSums() throws LiquibaseException { */ @Deprecated public final CheckSum calculateCheckSum(final String changeSetIdentifier) throws LiquibaseException { - CommandResults commandResults = new CommandScope("calculateChecksum") - .addArgumentValue(DbUrlConnectionCommandStep.DATABASE_ARG, database) - .addArgumentValue(CalculateChecksumCommandStep.CHANGESET_IDENTIFIER_ARG, changeSetIdentifier) - .addArgumentValue(CalculateChecksumCommandStep.CHANGELOG_FILE_ARG, this.changeLogFile) - .execute(); - return commandResults.getResult(CalculateChecksumCommandStep.CHECKSUM_RESULT); + String changeSetAttributes[] = changeSetIdentifier.split("::"); + //validate changeSet parameters and return an error or removed/ignore any other '::' occurrence when processing either a path, id or author. + return this.calculateCheckSum(changeSetAttributes[0], changeSetAttributes[1], changeSetAttributes[2]); } /** - * Calculates the checksum for the values that form a given identifier - * - * @deprecated Use {link {@link CommandScope(String)}. + * Calculate the checksum for a given changeset specified by path, changeset id and author */ - @Deprecated - public CheckSum calculateCheckSum(final String filename, final String id, final String author) + public CheckSum calculateCheckSum(final String changeSetPath, final String changeSetId, final String changeSetAuthor) throws LiquibaseException { - return this.calculateCheckSum(String.format("%s::%s::%s", filename, id, author)); + CommandResults commandResults = new CommandScope("calculateChecksum") + .addArgumentValue(DbUrlConnectionCommandStep.DATABASE_ARG, database) + .addArgumentValue(CalculateChecksumCommandStep.CHANGESET_PATH_ARG, changeSetPath) + .addArgumentValue(CalculateChecksumCommandStep.CHANGESET_ID_ARG, changeSetId) + .addArgumentValue(CalculateChecksumCommandStep.CHANGESET_AUTHOR_ARG, changeSetAuthor) + .addArgumentValue(CalculateChecksumCommandStep.CHANGESET_IDENTIFIER_ARG, String.format("%s::%s::%s", changeSetPath, changeSetId, changeSetAuthor)) + .addArgumentValue(CalculateChecksumCommandStep.CHANGELOG_FILE_ARG, this.changeLogFile) + .execute(); + return commandResults.getResult(CalculateChecksumCommandStep.CHECKSUM_RESULT); } @Deprecated diff --git a/liquibase-standard/src/main/java/liquibase/command/core/CalculateChecksumCommandStep.java b/liquibase-standard/src/main/java/liquibase/command/core/CalculateChecksumCommandStep.java index 8c61eb46c1f..7ae685a2373 100644 --- a/liquibase-standard/src/main/java/liquibase/command/core/CalculateChecksumCommandStep.java +++ b/liquibase-standard/src/main/java/liquibase/command/core/CalculateChecksumCommandStep.java @@ -3,11 +3,22 @@ import liquibase.ChecksumVersion; import liquibase.Scope; import liquibase.change.CheckSum; -import liquibase.changelog.*; -import liquibase.command.*; +import liquibase.changelog.ChangeLogHistoryService; +import liquibase.changelog.ChangeLogHistoryServiceFactory; +import liquibase.changelog.ChangeLogParameters; +import liquibase.changelog.ChangeSet; +import liquibase.changelog.DatabaseChangeLog; +import liquibase.changelog.RanChangeSet; +import liquibase.command.AbstractCommandStep; +import liquibase.command.CommandArgumentDefinition; +import liquibase.command.CommandBuilder; +import liquibase.command.CommandDefinition; +import liquibase.command.CommandResultDefinition; +import liquibase.command.CommandResultsBuilder; +import liquibase.command.CommandScope; +import liquibase.command.CommonArgumentNames; import liquibase.database.Database; import liquibase.exception.LiquibaseException; -import liquibase.integration.commandline.LiquibaseCommandLineConfiguration; import liquibase.parser.ChangeLogParserFactory; import liquibase.resource.ResourceAccessor; import liquibase.util.StringUtil; @@ -20,22 +31,41 @@ public class CalculateChecksumCommandStep extends AbstractCommandStep { protected static final String[] COMMAND_NAME = {"calculateChecksum"}; public static final CommandArgumentDefinition CHANGELOG_FILE_ARG; - public static final CommandArgumentDefinition CHANGESET_IDENTIFIER_ARG; + public static final CommandArgumentDefinition CHANGESET_PATH_ARG; + + public static final CommandArgumentDefinition CHANGESET_ID_ARG; + + public static final CommandArgumentDefinition CHANGESET_AUTHOR_ARG; public static final CommandResultDefinition CHECKSUM_RESULT; - protected static final int CHANGESET_ID_NUM_PARTS = 3; - protected static final int CHANGESET_ID_AUTHOR_PART = 2; - protected static final int CHANGESET_ID_CHANGESET_PART = 1; - protected static final int CHANGESET_ID_CHANGELOG_PART = 0; + public static final CommandArgumentDefinition CHANGESET_IDENTIFIER_ARG; + private static final int CHANGESET_IDENTIFIER_PARTS_LENGTH = 3; + private static final int CHANGESET_IDENTIFIER_AUTHOR_PART = 2; + private static final int CHANGESET_IDENTIFIER_ID_PART = 1; + private static final int CHANGESET_IDENTIFIER_PATH_PART = 0; static { CommandBuilder builder = new CommandBuilder(COMMAND_NAME); CHANGELOG_FILE_ARG = builder.argument(CommonArgumentNames.CHANGELOG_FILE, String.class).required() - .description("The root changelog file").build(); - CHANGESET_IDENTIFIER_ARG = builder.argument("changesetIdentifier", String.class).required() - .description("Changeset ID identifier of form filepath::id::author").build(); + .description("The root changelog file").build(); + + CHANGESET_IDENTIFIER_ARG = builder.argument("changesetIdentifier", String.class) + .description("ChangeSet identifier of form filepath::id::author") + .build(); + + CHANGESET_PATH_ARG = builder.argument("changesetPath", String.class) + .description("Changelog path in which the changeSet is included") + .build(); + + CHANGESET_ID_ARG = builder.argument("changesetId", String.class) + .description("ChangeSet ID attribute") + .build(); + + CHANGESET_AUTHOR_ARG = builder.argument("changesetAuthor", String.class) + .description("ChangeSet Author attribute") + .build(); CHECKSUM_RESULT = builder.result("checksumResult", CheckSum.class).description("Calculated checksum").build(); } @@ -47,42 +77,102 @@ public List> requiredDependencies() { @Override public String[][] defineCommandNames() { - return new String[][] { COMMAND_NAME }; + return new String[][]{COMMAND_NAME}; } @Override public void run(CommandResultsBuilder resultsBuilder) throws Exception { CommandScope commandScope = resultsBuilder.getCommandScope(); + final String changeSetIdentifier = commandScope.getArgumentValue(CHANGESET_IDENTIFIER_ARG); final String changeLogFile = commandScope.getArgumentValue(CHANGELOG_FILE_ARG).replace('\\', '/'); + String changeSetPath; + String changeSetId; + String changeSetAuthor; + + final boolean isChangeSetIdentifierPassed = changeSetIdentifier != null; + + validateIdentifierParameters(commandScope, changeSetIdentifier); + + if (isChangeSetIdentifierPassed) { + List parts = validateAndExtractParts(changeSetIdentifier, changeLogFile); + changeSetPath = parts.get(CHANGESET_IDENTIFIER_PATH_PART); + changeSetId = parts.get(CHANGESET_IDENTIFIER_ID_PART); + changeSetAuthor = parts.get(CHANGESET_IDENTIFIER_AUTHOR_PART); + } else { + changeSetPath = commandScope.getArgumentValue(CHANGESET_PATH_ARG); + changeSetId = commandScope.getArgumentValue(CHANGESET_ID_ARG); + changeSetAuthor = commandScope.getArgumentValue(CHANGESET_AUTHOR_ARG); + } + final Database database = (Database) commandScope.getDependency(Database.class); - List parts = validateAndExtractParts(changeSetIdentifier, changeLogFile); - Scope.getCurrentScope().getLog(getClass()).info(String.format("Calculating checksum for changeset %s", changeSetIdentifier)); + Scope.getCurrentScope().getLog(getClass()).info(String.format("Calculating checksum for changeSet identified by changeSet id: %s, author: %s, path: %s", + changeSetId, + changeSetAuthor, + changeSetPath + )); ResourceAccessor resourceAccessor = Scope.getCurrentScope().getResourceAccessor(); DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser( - changeLogFile, resourceAccessor).parse(changeLogFile, new ChangeLogParameters(database), resourceAccessor); + changeLogFile, resourceAccessor).parse(changeLogFile, new ChangeLogParameters(database), resourceAccessor); - ChangeSet changeSet = changeLog.getChangeSet(parts.get(CHANGESET_ID_CHANGELOG_PART), parts.get(CHANGESET_ID_AUTHOR_PART), - parts.get(CHANGESET_ID_CHANGESET_PART)); + ChangeSet changeSet = changeLog.getChangeSet(changeSetPath, changeSetAuthor, changeSetId); if (changeSet == null) { - throw new LiquibaseException(new IllegalArgumentException("No such changeSet: " + changeSetIdentifier)); + throw new LiquibaseException(new IllegalArgumentException(String.format("No such changeSet identified by changeSet id: %s, author: %s, path: %s", + changeSetId, + changeSetAuthor, + changeSetPath + ))); } ChangeLogHistoryService changeLogService = ChangeLogHistoryServiceFactory.getInstance().getChangeLogService(database); RanChangeSet ranChangeSet = changeLogService.getRanChangeSet(changeSet); sendMessages(resultsBuilder, changeSet.generateCheckSum( - ranChangeSet != null && ranChangeSet.getLastCheckSum() != null ? - ChecksumVersion.enumFromChecksumVersion(ranChangeSet.getLastCheckSum().getVersion()) : ChecksumVersion.latest() - ) + ranChangeSet != null && ranChangeSet.getLastCheckSum() != null ? + ChecksumVersion.enumFromChecksumVersion(ranChangeSet.getLastCheckSum().getVersion()) : ChecksumVersion.latest() + ) ); } - private static void sendMessages(CommandResultsBuilder resultsBuilder, CheckSum checkSum) { - resultsBuilder.addResult(CHECKSUM_RESULT, checkSum); - Scope.getCurrentScope().getUI().sendMessage(checkSum.toString()); + private void validateIdentifierParameters(CommandScope commandScope, String changeSetIdentifier) throws LiquibaseException { + final boolean isAmbiguousNumberOfIdentifierProvided = (commandScope.getArgumentValue(CHANGESET_ID_ARG) != null || + commandScope.getArgumentValue(CHANGESET_AUTHOR_ARG) != null || commandScope.getArgumentValue(CHANGESET_PATH_ARG) != null) + && changeSetIdentifier != null; + + if (isAmbiguousNumberOfIdentifierProvided) { + String errorMessage = "Error encountered while parsing the command line. " + + "'--changeset-identifier' cannot be provided alongside other changeset arguments: " + + "'--changeset-id', '--changeset-path', '--changeset-author'."; + throw new LiquibaseException(new IllegalArgumentException(errorMessage)); + } + + final boolean isRequiredCompositeIdentifierMissing = (commandScope.getArgumentValue(CHANGESET_ID_ARG) == null || + commandScope.getArgumentValue(CHANGESET_AUTHOR_ARG) == null || commandScope.getArgumentValue(CHANGESET_PATH_ARG) == null) + && changeSetIdentifier == null; + + if (isRequiredCompositeIdentifierMissing) { + String errorMessage = "Error encountered while parsing the command line. " + + "If --changeset-identifier is not provided than --changeset-id, --changeset-author and --changeset-path must be specified. " + + "Missing argument: "; + + if (commandScope.getArgumentValue(CHANGESET_ID_ARG) == null) { + errorMessage = errorMessage + " '--changeset-id',"; + } + + if (commandScope.getArgumentValue(CHANGESET_AUTHOR_ARG) == null) { + errorMessage = errorMessage + " '--changeset-author',"; + } + + if (commandScope.getArgumentValue(CHANGESET_PATH_ARG) == null) { + errorMessage = errorMessage + " '--changeset-path',"; + } + + errorMessage = errorMessage.substring(0,errorMessage.length() - 1) + "."; + + throw new LiquibaseException(new IllegalArgumentException(errorMessage)); + } } private List validateAndExtractParts(String changeSetIdentifier, String changeLogFile) throws LiquibaseException { @@ -95,7 +185,7 @@ private List validateAndExtractParts(String changeSetIdentifier, String } final List parts = StringUtil.splitAndTrim(changeSetIdentifier, "::"); - if ((parts == null) || (parts.size() < CHANGESET_ID_NUM_PARTS)) { + if ((parts == null) || (parts.size() < CHANGESET_IDENTIFIER_PARTS_LENGTH)) { throw new LiquibaseException( new IllegalArgumentException("Invalid changeSet identifier: " + changeSetIdentifier) ); @@ -103,9 +193,24 @@ private List validateAndExtractParts(String changeSetIdentifier, String return parts; } + private static void sendMessages(CommandResultsBuilder resultsBuilder, CheckSum checkSum) { + resultsBuilder.addResult(CHECKSUM_RESULT, checkSum); + Scope.getCurrentScope().getUI().sendMessage(checkSum.toString()); + } + @Override public void adjustCommandDefinition(CommandDefinition commandDefinition) { commandDefinition.setShortDescription("Calculates and prints a checksum for the changeset"); - commandDefinition.setLongDescription("Calculates and prints a checksum for the changeset with the given id in the format filepath::id::author"); + commandDefinition.setLongDescription( + "Calculates and prints a checksum for the changeset with the given id in the format filepath::id::author"); + commandDefinition.setHelpFooter("\nCalculate checksum provides two ways to identify a changeSet.\n\n" + + "1. Composite changeSet identifier\n\n" + + "The composite changeSet identifier must be passed in the following pattern myPath::myId::myAuthor.\n\n" + + "liquibase calculateCheckSum --changesetIdentifier myFile::myId::myAuthor\n\n" + + "2. Individual changeSet parameters\n\n" + + "The second option requires all three parameters to be defined.\n" + + "This variant offers some more flexibility in naming conventions for path, id and author.\n\n"+ + "liquibase calculateCheckSum --changesetId myId --changesetAuthor myAuthor --changesetPath myPath\n" + ); } } diff --git a/liquibase-standard/src/main/java/liquibase/integration/commandline/Main.java b/liquibase-standard/src/main/java/liquibase/integration/commandline/Main.java index 27d4c6dacb4..d96b2d103d7 100644 --- a/liquibase-standard/src/main/java/liquibase/integration/commandline/Main.java +++ b/liquibase-standard/src/main/java/liquibase/integration/commandline/Main.java @@ -2,7 +2,6 @@ import liquibase.*; import liquibase.changelog.ChangeLogParameters; -import liquibase.changelog.DatabaseChangeLog; import liquibase.changelog.visitor.ChangeExecListener; import liquibase.changelog.visitor.DefaultChangeExecListener; import liquibase.command.CommandResults; @@ -974,19 +973,7 @@ private void checkForMalformedCommandParameters(final List messages) { return; } - final int CHANGESET_MINIMUM_IDENTIFIER_PARTS = 3; - - if (COMMANDS.CALCULATE_CHECKSUM.equalsIgnoreCase(command)) { - for (final String param : commandParams) { - if ((param != null) && !param.startsWith("-")) { - final String[] parts = param.split("::"); - if (parts.length < CHANGESET_MINIMUM_IDENTIFIER_PARTS) { - messages.add(coreBundle.getString("changeset.identifier.must.have.form.filepath.id.author")); - break; - } - } - } - } else if (COMMANDS.DIFF_CHANGELOG.equalsIgnoreCase(command) && (diffTypes != null) && diffTypes.toLowerCase + if (COMMANDS.DIFF_CHANGELOG.equalsIgnoreCase(command) && (diffTypes != null) && diffTypes.toLowerCase ().contains("data")) { messages.add(String.format(coreBundle.getString("including.data.diffchangelog.has.no.effect"), OPTIONS.DIFF_TYPES, COMMANDS.GENERATE_CHANGELOG @@ -1514,7 +1501,17 @@ protected void doMigration() throws Exception { liquibase.clearCheckSums(); return; } else if (COMMANDS.CALCULATE_CHECKSUM.equalsIgnoreCase(command)) { - liquibase.calculateCheckSum(commandParams.iterator().next()); + CommandScope calculateChecksumCommand = new CommandScope("calculateChecksum"); + + calculateChecksumCommand + .addArgumentValue(DbUrlConnectionCommandStep.DATABASE_ARG, database) + .addArgumentValue(CalculateChecksumCommandStep.CHANGESET_PATH_ARG, getCommandParam(OPTIONS.CHANGE_SET_PATH, null)) + .addArgumentValue(CalculateChecksumCommandStep.CHANGESET_ID_ARG, getCommandParam(OPTIONS.CHANGE_SET_ID, null)) + .addArgumentValue(CalculateChecksumCommandStep.CHANGESET_AUTHOR_ARG, getCommandParam(OPTIONS.CHANGE_SET_AUTHOR, null)) + .addArgumentValue(CalculateChecksumCommandStep.CHANGESET_IDENTIFIER_ARG, getCommandParam(OPTIONS.CHANGE_SET_IDENTIFIER, null)) + .addArgumentValue(CalculateChecksumCommandStep.CHANGELOG_FILE_ARG, this.changeLogFile); + + calculateChecksumCommand.execute(); return; } else if (COMMANDS.DB_DOC.equalsIgnoreCase(command)) { if (commandParams.isEmpty()) { @@ -2186,6 +2183,8 @@ private enum OPTIONS { private static final String CHANGELOG_FILE = "changeLogFile"; private static final String DATA_OUTPUT_DIRECTORY = "dataOutputDirectory"; private static final String DIFF_TYPES = "diffTypes"; + + public static final String CHANGE_SET_IDENTIFIER = "changeSetIdentifier"; private static final String CHANGE_SET_ID = "changeSetId"; private static final String CHANGE_SET_AUTHOR = "changeSetAuthor"; private static final String CHANGE_SET_PATH = "changeSetPath";