Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split calculate-checksum parameter changeSetIdentifier into changeSetPath / changesetId / changeSetAuthor #4463

Merged
merged 10 commits into from
Oct 9, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,7 +37,7 @@ Optional Args:
Default: null
"""

run "Happy path", {
run "Happy path using changeSetIdentifier", {
arguments = [
url : { it.altUrl },
username : { it.altUsername },
Expand All @@ -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", {
Expand Down
26 changes: 14 additions & 12 deletions liquibase-standard/src/main/java/liquibase/Liquibase.java
Original file line number Diff line number Diff line change
Expand Up @@ -1230,23 +1230,25 @@ public void clearCheckSums() throws LiquibaseException {
*/
@Deprecated
public final CheckSum calculateCheckSum(final String changeSetIdentifier) throws LiquibaseException {
filipelautert marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,22 +31,41 @@ public class CalculateChecksumCommandStep extends AbstractCommandStep {
protected static final String[] COMMAND_NAME = {"calculateChecksum"};

public static final CommandArgumentDefinition<String> CHANGELOG_FILE_ARG;
public static final CommandArgumentDefinition<String> CHANGESET_IDENTIFIER_ARG;
public static final CommandArgumentDefinition<String> CHANGESET_PATH_ARG;

public static final CommandArgumentDefinition<String> CHANGESET_ID_ARG;

public static final CommandArgumentDefinition<String> CHANGESET_AUTHOR_ARG;

public static final CommandResultDefinition<CheckSum> 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<String> 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();
}
Expand All @@ -47,42 +77,102 @@ public List<Class<?>> 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JulienMa94 I extracted the parameters validation to a new method so it's easier to spot this change.


if (isChangeSetIdentifierPassed) {
List<String> 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<String> 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<String> validateAndExtractParts(String changeSetIdentifier, String changeLogFile) throws LiquibaseException {
Expand All @@ -95,17 +185,32 @@ private List<String> validateAndExtractParts(String changeSetIdentifier, String
}

final List<String> 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)
);
}
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"
);
}
}