From d100bbb6c984c5f2cffec791b1a93e76a14aab67 Mon Sep 17 00:00:00 2001 From: filipe Date: Fri, 5 May 2023 17:02:32 -0300 Subject: [PATCH] DAT-13285 - Fix CheckSum generation issues + improvement (#3914) * Compute checksum method updated * Checksum logic updated for visitors and changes. * GenerateCheckSum field filter logic updated, plus refactoring on generateCheckSum logic from createProcedure change * generateCheckSum code refactoring done for Create Procedure, updated same method logic for CreateView change, plus some minor refactorings. * Fixed unit tests. * Added createProcedure and changeSet unit tests * Added back procedure text null check. * Null check added on normalizing stream constructor method. * Correction to previous commmit on null check added on normalizing stream constructor * CURRENT_CHECKSUM_ALGORITHM_VERSION updated from 8 to 9. * Update checksum test assertions to match with algorithm version change * Review comments applied. * Update encoding configuration for CreateProcedure change. * CreateProcedureChange test name updated. * Update calculateCheckSum command tests to upgrade expected checksum which changed after upgrading algorithm version. * CreateProcedure and CreateView tests added, plus minor refactorings. * Merge * Updates checksum for tests. * Exclude fields for PRO change types to avoid recalculating checksum when there are not significant changes. * Added a generic generateCheckSum method to use it across some different change types extending from CreateProcedureChange. * Add missing parameter * - Update change objects (CreateProcedure, CreateView and SQLFile) to make use properties updates when using a SQL file - Added unit tests for changes made. * - Support added to exclude change set attribute fields not needed when computing checksum. - Extract expanding stream content in to a separate component. * - Undo fields exclusion from generateCheckSum (AbstractSQLChange). - PropertyExpandingStream refactoring. * - New checksum unit tests added for some more change attributes for CreateProcedure and CreateView. - Minor refactoring done on tests. * Undo db change property changes recently done from CreateProcedure change. * DAT-14642 - Prevents Liquibase from incorrectly attempting to redeploy changesets during checksum upgrade (#4193) * Makes sure that we reset ChangeLogHistoryServiceFactory after upgradingCheckSums. * Rollback DropAllCommand.java changes. * Trigger Build * Rollback changes * Makes ReleaseLocks Command checksum upgrade consistent with ListLocks * Fix null md5sum issues during checksum upgrades * When updating checksums use the stored file path instead of the generated one as we changed the way that we store paths during the years * Fix validate command * Fix unexpected-changesets command * Adding unit tests for PR 4210 . --------- Co-authored-by: Sayali M --------- Co-authored-by: Daniel Mallorga Co-authored-by: Daniel Mallorga <75833793+MalloD12@users.noreply.github.com> Co-authored-by: filipe Co-authored-by: Sayali M Co-authored-by: suryaaki2 <80348493+suryaaki2@users.noreply.github.com> --- .../MarkChangeSetRanExecuteTest.java | 46 +++--- .../command/calculateChecksum.test.groovy | 2 +- .../command/updateCountSql.test.groovy | 2 +- .../src/main/java/liquibase/Liquibase.java | 2 + .../java/liquibase/change/AbstractChange.java | 15 +- .../liquibase/change/AbstractSQLChange.java | 82 ++-------- .../main/java/liquibase/change/CheckSum.java | 8 +- .../change/core/CreateProcedureChange.java | 78 ++++++---- .../change/core/CreateViewChange.java | 53 ++++--- .../liquibase/change/core/SQLFileChange.java | 29 +++- .../changelog/ChangeLogIterator.java | 2 - .../java/liquibase/changelog/ChangeSet.java | 32 ++-- .../changelog/DatabaseChangeLog.java | 4 +- .../changelog/PropertyExpandingStream.java | 71 +++++++++ .../changelog/filter/DbmsChangeSetFilter.java | 12 +- .../filter/ShouldRunChangeSetFilter.java | 3 + .../command/core/ReleaseLocksCommandStep.java | 3 + .../sql/visitor/AbstractSqlVisitor.java | 19 ++- .../UpdateChangeSetChecksumGenerator.java | 12 +- .../change/AbstractChangeTest.groovy | 56 ++++++- .../core/CreateProcedureChangeTest.groovy | 146 +++++++++++++++++- .../change/core/CreateViewChangeTest.groovy | 101 +++++++++++- .../change/core/SQLFileChangeTest.groovy | 26 +++- .../ShouldRunChangeSetFilterTest.groovy | 16 +- .../liquibase/hub/model/HubChangeTest.groovy | 4 +- .../change/AbstractSQLChangeTest.java | 28 ++-- .../java/liquibase/change/CheckSumTest.java | 2 +- .../UpdateChangeSetChecksumGeneratorTest.java | 97 ++++++++++++ 28 files changed, 740 insertions(+), 211 deletions(-) create mode 100644 liquibase-standard/src/main/java/liquibase/changelog/PropertyExpandingStream.java create mode 100644 liquibase-standard/src/test/java/liquibase/sqlgenerator/core/UpdateChangeSetChecksumGeneratorTest.java diff --git a/liquibase-integration-tests/src/test/java/liquibase/statementexecute/MarkChangeSetRanExecuteTest.java b/liquibase-integration-tests/src/test/java/liquibase/statementexecute/MarkChangeSetRanExecuteTest.java index 82ad1f0bf1f..521ea32fe87 100644 --- a/liquibase-integration-tests/src/test/java/liquibase/statementexecute/MarkChangeSetRanExecuteTest.java +++ b/liquibase-integration-tests/src/test/java/liquibase/statementexecute/MarkChangeSetRanExecuteTest.java @@ -35,85 +35,85 @@ public void generateSql_insert() throws Exception { assertCorrect("insert into [databasechangelog] ([id], [author], [filename], [dateexecuted], " + "[orderexecuted], [md5sum], [description], [comments], [exectype], [contexts], [labels], " + "[liquibase], [deployment_id]) values ('a', 'b', 'c', getdate(), 1, " + - "'8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + + "'9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + " null)", MSSQLDatabase.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, description, comments, exectype, contexts, labels, liquibase, deployment_id) values " + - "('a', 'b', 'c', systimestamp, 1, '8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', " + + "('a', 'b', 'c', systimestamp, 1, '9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', " + "'executed', 'e', null, '" + version + "', null)", OracleDatabase.class); assertCorrect("insert into [databasechangelog] ([id], [author], [filename], [dateexecuted], " + "[orderexecuted], [md5sum], [description], [comments], [exectype], [contexts], [labels], " + "[liquibase], [deployment_id]) values ('a', 'b', 'c', getdate(), 1, " + - "'8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + + "'9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + " null)", SybaseDatabase.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, description, comments, exectype, contexts, labels, liquibase, deployment_id) values " + "('a', 'b', 'c', " + - "current year to fraction(5), 1, '8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', " + + "current year to fraction(5), 1, '9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', " + "'executed', " + "'e', null, '" + version + "', null)", InformixDatabase.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, description, comments, exectype, contexts, labels, liquibase, deployment_id) values " + "('a', 'b', 'c', current timestamp, 1, " + - "'8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + + "'9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + " null)", DB2Database.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, description, comments, exectype, contexts, labels, liquibase, deployment_id) values " + "('a', 'b', 'c', current_timestamp, 1, " + - "'8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + + "'9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + " null)", FirebirdDatabase.class, DerbyDatabase.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, description, comments, exectype, contexts, labels, liquibase, deployment_id) values " + "('a', 'b', 'c', now, 1, " + - "'8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + + "'9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + " null)", HsqlDatabase.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, description, comments, exectype, contexts, labels, liquibase, deployment_id) values " + "('a', 'b', 'c', now(), 1, " + - "'8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + + "'9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + " null)", SybaseASADatabase.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, `description`, comments, exectype, contexts, labels, liquibase, deployment_id) values " + "('a', 'b', 'c', now(), 1, " + - "'8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + + "'9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + " null)", MySQLDatabase.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, `description`, comments, exectype, contexts, labels, liquibase, deployment_id) values " + "('a', 'b', 'c', now(), 1, " + - "'8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + + "'9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + " null)", MariaDBDatabase.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, description, comments, exectype, contexts, labels, liquibase, deployment_id) values " + "('a', 'b', 'c', now(), 1, " + - "'8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + + "'9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + " null)", PostgresDatabase.class, H2Database.class, CockroachDatabase.class, EnterpriseDBDatabase.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, description, comments, exectype, contexts, labels, liquibase, deployment_id) values " + "('a', 'b', 'c', date('now'), 1, " + - "'8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + + "'9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, '" + version + "'," + " null)", Ingres9Database.class); assertCorrect("insert into databasechangelog (id, author, filename, dateexecuted, orderexecuted, " + "md5sum, description, comments, exectype, contexts, labels, liquibase, deployment_id) values " + "('a', 'b', 'c', current_timestamp::timestamp_ntz, 1," + - " '8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, " + + " '9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, " + "'" + version + "', null)", SnowflakeDatabase.class); assertCorrectOnRest("insert into databasechangelog (id, author, filename, dateexecuted, " + "orderexecuted, md5sum, description, comments, exectype, contexts, labels, liquibase, deployment_id) " + "values ('a', 'b', 'c', " + - "current timestamp, 1, '8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, " + + "current timestamp, 1, '9:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'executed', 'e', null, " + "'" + version + "', null)"); } @@ -124,39 +124,39 @@ public void generateSql_update() throws Exception { this.statementUnderTest = new MarkChangeSetRanStatement(new ChangeSet("a", "b", false, false, "c", "e", "f", null), ChangeSet.ExecType.RERAN); assertCorrect("update [databasechangelog] set [comments] = '', [contexts] = 'e', [dateexecuted] = getdate(), [deployment_id] = null, [exectype] " + - "= 'reran', [labels] = null, [md5sum] = '8:d41d8cd98f00b204e9800998ecf8427e', [orderexecuted] = 1 where [id] =" + + "= 'reran', [labels] = null, [md5sum] = '9:d41d8cd98f00b204e9800998ecf8427e', [orderexecuted] = 1 where [id] =" + " 'a' and" + " [author] = 'b' and [filename] = 'c'", MSSQLDatabase.class); assertCorrect("update databasechangelog set comments = '', contexts = 'e', dateexecuted = systimestamp, deployment_id = null, exectype = " + - "'reran', labels = null, md5sum = '8:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where id = 'a' and" + + "'reran', labels = null, md5sum = '9:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where id = 'a' and" + " author " + "= 'b' and filename = 'c'", OracleDatabase.class); assertCorrect("update [databasechangelog] set [comments] = '', [contexts] = 'e', [dateexecuted] = getdate(), [deployment_id] = null, [exectype] " + - "= 'reran', [labels] = null, [md5sum] = '8:d41d8cd98f00b204e9800998ecf8427e', [orderexecuted] = 1 where [id] = 'a' and" + + "= 'reran', [labels] = null, [md5sum] = '9:d41d8cd98f00b204e9800998ecf8427e', [orderexecuted] = 1 where [id] = 'a' and" + " [author] = 'b' and [filename] = 'c'", SybaseDatabase.class); assertCorrect("update [databasechangelog] set [comments] = '', [contexts] = 'e', [dateexecuted] = current year to fraction(5), deployment_id = " + - "null, exectype = 'reran', [labels] = null, md5sum = '8:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where id " + + "null, exectype = 'reran', [labels] = null, md5sum = '9:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where id " + "= 'a' and author = 'b' and filename = 'c'", InformixDatabase.class); assertCorrect("update [databasechangelog] set [comments] = '', [contexts] = 'e', [dateexecuted] = current timestamp, deployment_id = null, " + - "exectype = 'reran', [labels] = null, md5sum = '8:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where " + + "exectype = 'reran', [labels] = null, md5sum = '9:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where " + "id = 'a' and author = 'b' and filename = 'c'", DB2Database.class); assertCorrect("update [databasechangelog] set [comments] = '', [contexts] = 'e', [dateexecuted] = current_timestamp, deployment_id = null, " + - "exectype = 'reran', [labels] = null, md5sum = '8:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where " + + "exectype = 'reran', [labels] = null, md5sum = '9:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where " + "id = 'a' and author = 'b' and filename = 'c'", FirebirdDatabase.class, DerbyDatabase.class); assertCorrect("update [databasechangelog] set [comments] = '', [contexts] = 'e', [dateexecuted] = NOW(), deployment_id = null, exectype = " + - "'reran', [labels] = null, md5sum = '8:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where id = 'a' and" + + "'reran', [labels] = null, md5sum = '9:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where id = 'a' and" + " author = 'b' and filename = 'c'", SybaseASADatabase.class); assertCorrect("update [databasechangelog] set [comments] = '', [contexts] = 'e', [dateexecuted] = NOW(), deployment_id = null, exectype = " + - "'reran', [labels] = null, md5sum = '8:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where id = 'a' and" + + "'reran', [labels] = null, md5sum = '9:d41d8cd98f00b204e9800998ecf8427e', orderexecuted = 1 where id = 'a' and" + " author = 'b' and filename = 'c'", MySQLDatabase.class, MariaDBDatabase.class, HsqlDatabase.class, PostgresDatabase.class, H2Database.class, CockroachDatabase.class); assertCorrectOnRest("update [databasechangelog] set [comments] = '', [contexts] = 'e', [dateexecuted] = NOW(), [deployment_id] = null, [exectype] = 'reran', [labels] = null, [md5sum] = " + - "'8:d41d8cd98f00b204e9800998ecf8427e', [orderexecuted] = 1 where id = 'a' and author = 'b' and filename = 'c'"); + "'9:d41d8cd98f00b204e9800998ecf8427e', [orderexecuted] = 1 where id = 'a' and author = 'b' and filename = 'c'"); } } 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 17d28a290f8..f660927c23c 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 @@ -40,7 +40,7 @@ Optional Args: ] expectedResults = [ - checksumResult : "8:b6084e5d5f46b534bbbe18a0d35d34e0" + checksumResult : "9:10de8cd690aed1d88d837cbe555d1684" ] } diff --git a/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/updateCountSql.test.groovy b/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/updateCountSql.test.groovy index f47001b753e..f7c78739d8e 100644 --- a/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/updateCountSql.test.groovy +++ b/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/updateCountSql.test.groovy @@ -62,7 +62,7 @@ Optional Args: -- They aren't used to compute the changeSet MD5Sum, so you can update them whenever you want without causing problems. CREATE TABLE PUBLIC.person (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, firstname VARCHAR(50), lastname VARCHAR(50) NOT NULL, CONSTRAINT PK_PERSON PRIMARY KEY (id)); -INSERT INTO PUBLIC.DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, DESCRIPTION, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('1', 'nvoxland', 'changelogs/h2/complete/simple.changelog.xml', NOW(), 1, '8:b6084e5d5f46b534bbbe18a0d35d34e0', 'createTable tableName=person', 'You can add comments to changeSets. +INSERT INTO PUBLIC.DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, DESCRIPTION, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('1', 'nvoxland', 'changelogs/h2/complete/simple.changelog.xml', NOW(), 1, '9:10de8cd690aed1d88d837cbe555d1684', 'createTable tableName=person', 'You can add comments to changeSets. They can even be multiple lines if you would like. They aren''t used to compute the changeSet MD5Sum, so you can update them whenever you want without causing problems.', 'EXECUTED', NULL, NULL, 'DEV' """ diff --git a/liquibase-standard/src/main/java/liquibase/Liquibase.java b/liquibase-standard/src/main/java/liquibase/Liquibase.java index f2d87371393..2c4d9bc87f0 100644 --- a/liquibase-standard/src/main/java/liquibase/Liquibase.java +++ b/liquibase-standard/src/main/java/liquibase/Liquibase.java @@ -1397,6 +1397,7 @@ public void reportUnexpectedChangeSets(boolean verbose, Contexts contexts, Label Writer out) throws LiquibaseException { changeLogParameters.setContexts(contexts); changeLogParameters.setLabels(labelExpression); + checkLiquibaseTables(false, getDatabaseChangeLog(true), null, null); try { Collection unexpectedChangeSets = listUnexpectedChangeSets(contexts, labelExpression); @@ -1540,6 +1541,7 @@ public DiffResult diff(Database referenceDatabase, Database targetDatabase, Comp */ public void validate() throws LiquibaseException { DatabaseChangeLog changeLog = getDatabaseChangeLog(true); + checkLiquibaseTables(false, changeLog, null, null); if (changeLog != null) { changeLog.validate(database); } diff --git a/liquibase-standard/src/main/java/liquibase/change/AbstractChange.java b/liquibase-standard/src/main/java/liquibase/change/AbstractChange.java index 21e21d95a5b..3e7334007d6 100644 --- a/liquibase-standard/src/main/java/liquibase/change/AbstractChange.java +++ b/liquibase-standard/src/main/java/liquibase/change/AbstractChange.java @@ -489,7 +489,19 @@ public boolean supportsRollback(Database database) { */ @Override public CheckSum generateCheckSum() { - return CheckSum.compute(new StringChangeLogSerializer().serialize(this, false)); + return CheckSum.compute(new StringChangeLogSerializer(new StringChangeLogSerializer.FieldFilter() { + @Override + public boolean include(Object obj, String field, Object value) { + if(Arrays.stream(getExcludedFieldFilters()).anyMatch(filter -> filter.equals(field))) { + return false; + } + return super.include(obj, field, value); + } + }).serialize(this, false)); + } + + public String[] getExcludedFieldFilters() { + return new String[0]; } /* @@ -818,4 +830,5 @@ public String getDescription() { return description; } + } diff --git a/liquibase-standard/src/main/java/liquibase/change/AbstractSQLChange.java b/liquibase-standard/src/main/java/liquibase/change/AbstractSQLChange.java index c1868d14dc6..22bdca0062f 100644 --- a/liquibase-standard/src/main/java/liquibase/change/AbstractSQLChange.java +++ b/liquibase-standard/src/main/java/liquibase/change/AbstractSQLChange.java @@ -3,6 +3,7 @@ import liquibase.change.core.RawSQLChange; import liquibase.Scope; import liquibase.GlobalConfiguration; +import liquibase.change.core.SQLFileChange; import liquibase.database.Database; import liquibase.database.core.Db2zDatabase; import liquibase.database.core.MSSQLDatabase; @@ -10,6 +11,7 @@ import liquibase.exception.UnexpectedLiquibaseException; import liquibase.exception.ValidationErrors; import liquibase.exception.Warnings; +import liquibase.serializer.core.string.StringChangeLogSerializer; import liquibase.statement.SqlStatement; import liquibase.statement.core.RawCompoundStatement; import liquibase.statement.core.RawSqlStatement; @@ -17,6 +19,7 @@ import java.io.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static liquibase.statement.SqlStatement.EMPTY_SQL_STATEMENT; @@ -201,11 +204,12 @@ public CheckSum generateCheckSum() { } if (sql != null) { - stream = new ByteArrayInputStream(sql.getBytes(GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue()) + stream = new ByteArrayInputStream(sql.getBytes(GlobalConfiguration.FILE_ENCODING.getCurrentValue()) ); } - return CheckSum.compute(new NormalizingStream(this.getEndDelimiter(), this.isSplitStatements(), this.isStripComments(), stream), false); + return CheckSum.compute(new AbstractSQLChange.NormalizingStream(stream), false); + } catch (IOException e) { throw new UnexpectedLiquibaseException(e); } finally { @@ -286,8 +290,7 @@ protected String normalizeLineEndings(String string) { } public static class NormalizingStream extends InputStream { - private ByteArrayInputStream headerStream; - private PushbackInputStream stream; + private InputStream stream; private byte[] quickBuffer = new byte[100]; private List resizingBuffer = new ArrayList<>(); @@ -296,44 +299,25 @@ public static class NormalizingStream extends InputStream { private int lastChar = 'X'; private boolean seenNonSpace; + @Deprecated public NormalizingStream(String endDelimiter, Boolean splitStatements, Boolean stripComments, InputStream stream) { - this.stream = new PushbackInputStream(stream, 2048); - try { - this.headerStream = new ByteArrayInputStream((endDelimiter+":"+splitStatements+":"+stripComments+":").getBytes(GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue())); - } catch (UnsupportedEncodingException e) { - throw new UnexpectedLiquibaseException(e); + this(stream); + } + + public NormalizingStream(InputStream stream) { + if(stream == null) { + stream = new ByteArrayInputStream(new byte[0]); } + this.stream = new PushbackInputStream(stream, 2048); } @Override public int read() throws IOException { - if (headerStream != null) { - int returnChar = headerStream.read(); - if (returnChar != -1) { - return returnChar; - } - headerStream = null; - } int returnChar = stream.read(); - if (isWhiteSpace(returnChar)) { - returnChar = ' '; - } - while ((returnChar == ' ') && (!seenNonSpace || (lastChar == ' '))) { + while (isWhiteSpace(returnChar)) { returnChar = stream.read(); - - if (isWhiteSpace(returnChar)) { - returnChar = ' '; - } - } - - seenNonSpace = true; - - lastChar = returnChar; - - if ((lastChar == ' ') && isOnlyWhitespaceRemaining()) { - return -1; } return returnChar; @@ -359,40 +343,6 @@ public synchronized void reset() throws IOException { stream.reset(); } - private boolean isOnlyWhitespaceRemaining() throws IOException { - try { - int quickBufferUsed = 0; - while (true) { - byte read = (byte) stream.read(); - if (quickBufferUsed >= quickBuffer.length) { - resizingBuffer.add(read); - } else { - quickBuffer[quickBufferUsed++] = read; - } - - if (read == -1) { - return true; - } - if (!isWhiteSpace(read)) { - if (!resizingBuffer.isEmpty()) { - - byte[] buf = new byte[resizingBuffer.size()]; - for (int i=0; i< resizingBuffer.size(); i++) { - buf[i] = resizingBuffer.get(i); - } - - stream.unread(buf); - } - - stream.unread(quickBuffer, 0, quickBufferUsed); - return false; - } - } - } finally { - resizingBuffer.clear(); - } - } - private boolean isWhiteSpace(int read) { return (read == ' ') || (read == '\n') || (read == '\r') || (read == '\t'); } diff --git a/liquibase-standard/src/main/java/liquibase/change/CheckSum.java b/liquibase-standard/src/main/java/liquibase/change/CheckSum.java index 2669fede13a..1fb6db3a756 100644 --- a/liquibase-standard/src/main/java/liquibase/change/CheckSum.java +++ b/liquibase-standard/src/main/java/liquibase/change/CheckSum.java @@ -22,7 +22,7 @@ public final class CheckSum { private int version; private String storedCheckSum; - private static final int CURRENT_CHECKSUM_ALGORITHM_VERSION = 8; + private static final int CURRENT_CHECKSUM_ALGORITHM_VERSION = 9; private static final char DELIMITER = ':'; private static final String CHECKSUM_REGEX = "(^\\d)" + DELIMITER + "([a-zA-Z0-9]++)"; private static final Pattern CHECKSUM_PATTERN = Pattern.compile(CHECKSUM_REGEX); @@ -70,10 +70,8 @@ public static int getCurrentVersion() { public static CheckSum compute(String valueToChecksum) { return new CheckSum(MD5Util.computeMD5( //remove "Unknown" unicode char 65533 - Normalizer.normalize( - StringUtil.standardizeLineEndings(valueToChecksum) - .replace("\uFFFD", "") - , Normalizer.Form.NFC) + Normalizer.normalize(StringUtil.standardizeLineEndings(valueToChecksum) + .replace("\uFFFD", ""), Normalizer.Form.NFC) ), getCurrentVersion()); } diff --git a/liquibase-standard/src/main/java/liquibase/change/core/CreateProcedureChange.java b/liquibase-standard/src/main/java/liquibase/change/core/CreateProcedureChange.java index cd07ec73253..c38b2226c94 100644 --- a/liquibase-standard/src/main/java/liquibase/change/core/CreateProcedureChange.java +++ b/liquibase-standard/src/main/java/liquibase/change/core/CreateProcedureChange.java @@ -4,6 +4,7 @@ import liquibase.Scope; import liquibase.change.*; import liquibase.changelog.ChangeLogParameters; +import liquibase.changelog.PropertyExpandingStream; import liquibase.database.Database; import liquibase.database.DatabaseList; import liquibase.database.core.*; @@ -19,7 +20,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.util.Map; @DatabaseChange(name = "createProcedure", description = "Defines a stored procedure.", priority = ChangeMetaData.PRIORITY_DEFAULT) @@ -105,7 +106,7 @@ public void setRelativeToChangelogFile(Boolean relativeToChangelogFile) { this.relativeToChangelogFile = relativeToChangelogFile; } - @DatabaseChangeProperty(serializationType = SerializationType.DIRECT_VALUE) + @DatabaseChangeProperty(isChangeProperty = false) /** * @deprecated Use getProcedureText() instead */ @@ -124,7 +125,7 @@ public void setProcedureBody(String procedureText) { @DatabaseChangeProperty( description = "The SQL creating the procedure. You need to define either this attribute or 'path'. " + "procedureText is not supported in the XML format; however, you can specify the procedure SQL inline within the createProcedure definition.", - isChangeProperty = false) + serializationType = SerializationType.DIRECT_VALUE) public String getProcedureText() { return procedureText; } @@ -238,39 +239,31 @@ public InputStream openSqlStream() throws IOException { */ @Override public CheckSum generateCheckSum() { - if (this.path == null) { - return super.generateCheckSum(); - } + return generateCheckSum(this.procedureText); + } + protected CheckSum generateCheckSum(String sqlText) { InputStream stream = null; + CheckSum checkSum; try { - stream = openSqlStream(); - } catch (IOException e) { - throw new UnexpectedLiquibaseException(e); - } - - try { - String procedureText = this.procedureText; - if ((stream == null) && (procedureText == null)) { - procedureText = ""; - } - - String encoding = GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue(); - if (procedureText != null) { - try { - stream = new ByteArrayInputStream(procedureText.getBytes(encoding)); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(encoding + - " is not supported by the JVM, this should not happen according to the JavaDoc of " + - "the Charset class" - ); + if (getPath() == null) { + String procedureText = sqlText; + Charset encoding = GlobalConfiguration.FILE_ENCODING.getCurrentValue(); + if (procedureText != null) { + stream = new ByteArrayInputStream(sqlText.getBytes(encoding)); } } - - CheckSum checkSum = CheckSum.compute(new AbstractSQLChange.NormalizingStream(";", false, false, stream), false); - + else { + stream = openSqlStream(); + stream = new PropertyExpandingStream(this.getChangeSet(), stream); + } + checkSum = CheckSum.compute(new AbstractSQLChange.NormalizingStream(stream), false); return CheckSum.compute(super.generateCheckSum().toString() + ":" + checkSum); - } finally { + + } catch (IOException e) { + throw new UnexpectedLiquibaseException(e); + } + finally { if (stream != null) { try { stream.close(); @@ -279,7 +272,32 @@ public CheckSum generateCheckSum() { } } } + } + /** + * Listing SQL content fields (for example procedureText, triggerBody, etc.) we don't want to include as part of + * the checksum computes, because have a separate part that computes that checksum for that part doing the + * "normalizing" logic, so it is not impacted by the reformatting of the SQL. We are also excluding fields from the + * checksum generation which does not have a direct impact on the DB, such as dbms, path, comments, etc. + * + * Besides it has an impact on the DB, we have decided to do not add replaceIfExists as part of this list of fields + * as we are already avoiding the recalculation of the checksum by listing the main content fields of the different + * change types. + */ + @Override + public String[] getExcludedFieldFilters() { + return new String[]{ + "path", + "dbms", + "relativeToChangelogFile", + "procedureText", + "encoding", + "comments", + "triggerBody", + "functionBody", + "packageText", + "packageBodyText" + }; } @Override diff --git a/liquibase-standard/src/main/java/liquibase/change/core/CreateViewChange.java b/liquibase-standard/src/main/java/liquibase/change/core/CreateViewChange.java index a48fdae53ff..f3b52966385 100644 --- a/liquibase-standard/src/main/java/liquibase/change/core/CreateViewChange.java +++ b/liquibase-standard/src/main/java/liquibase/change/core/CreateViewChange.java @@ -4,6 +4,7 @@ import liquibase.Scope; import liquibase.change.*; import liquibase.changelog.ChangeLogParameters; +import liquibase.changelog.PropertyExpandingStream; import liquibase.database.Database; import liquibase.database.core.*; import liquibase.exception.UnexpectedLiquibaseException; @@ -14,7 +15,9 @@ import liquibase.snapshot.SnapshotGeneratorFactory; import liquibase.sqlgenerator.SqlGeneratorFactory; import liquibase.statement.SqlStatement; -import liquibase.statement.core.*; +import liquibase.statement.core.CreateViewStatement; +import liquibase.statement.core.DropViewStatement; +import liquibase.statement.core.SetViewRemarksStatement; import liquibase.structure.core.View; import liquibase.util.FileUtil; import liquibase.util.ObjectUtil; @@ -24,7 +27,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -182,6 +185,16 @@ protected InputStream openSqlStream() throws IOException { } } + @Override + public String[] getExcludedFieldFilters() { + return new String[] { + "path", + "relativeToChangelogFile", + "selectQuery", + "encoding" + }; + } + /** * Calculates the checksum based on the contained SQL. * @@ -189,35 +202,25 @@ protected InputStream openSqlStream() throws IOException { */ @Override public CheckSum generateCheckSum() { - if (this.path == null) { - return super.generateCheckSum(); - } - InputStream stream = null; + CheckSum checkSum; try { - stream = openSqlStream(); - } catch (IOException e) { - throw new UnexpectedLiquibaseException(e); - } - - try { - String selectQuery = this.selectQuery; - if ((stream == null) && (selectQuery == null)) { - selectQuery = ""; - } - - String encoding = GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue(); - if (selectQuery != null) { - try { + if (getPath() == null) { + String selectQuery = this.selectQuery; + Charset encoding = GlobalConfiguration.FILE_ENCODING.getCurrentValue(); + if(selectQuery != null) { stream = new ByteArrayInputStream(selectQuery.getBytes(encoding)); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(encoding+" is not supported by the JVM, this should not happen according to the JavaDoc of the Charset class"); } } - - CheckSum checkSum = CheckSum.compute(new AbstractSQLChange.NormalizingStream(";", false, false, stream), false); - + else { + stream = openSqlStream(); + stream = new PropertyExpandingStream(this.getChangeSet(), stream); + } + checkSum = CheckSum.compute(new AbstractSQLChange.NormalizingStream(stream), false); return CheckSum.compute(super.generateCheckSum().toString() + ":" + checkSum); + + } catch (IOException e) { + throw new UnexpectedLiquibaseException(e); } finally { if (stream != null) { try { diff --git a/liquibase-standard/src/main/java/liquibase/change/core/SQLFileChange.java b/liquibase-standard/src/main/java/liquibase/change/core/SQLFileChange.java index 24acd8b1377..1538554c1d0 100644 --- a/liquibase-standard/src/main/java/liquibase/change/core/SQLFileChange.java +++ b/liquibase-standard/src/main/java/liquibase/change/core/SQLFileChange.java @@ -1,10 +1,8 @@ package liquibase.change.core; +import liquibase.GlobalConfiguration; import liquibase.Scope; -import liquibase.change.AbstractSQLChange; -import liquibase.change.ChangeMetaData; -import liquibase.change.DatabaseChange; -import liquibase.change.DatabaseChangeProperty; +import liquibase.change.*; import liquibase.changelog.ChangeLogParameters; import liquibase.database.Database; import liquibase.exception.SetupException; @@ -15,8 +13,10 @@ import liquibase.util.StreamUtil; import liquibase.util.StringUtil; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; /** * Represents a Change for custom SQL stored in a File. @@ -161,4 +161,25 @@ public String describe() { ", relativeToChangelogFile=" + relativeToChangelogFile + '}'; } + + @Override + public CheckSum generateCheckSum() { + InputStream stream = null; + try { + String sqlContent = getSql(); + Charset encoding = GlobalConfiguration.FILE_ENCODING.getCurrentValue(); + stream = new ByteArrayInputStream(sqlContent.getBytes(encoding)); + CheckSum checkSum = CheckSum.compute(new AbstractSQLChange.NormalizingStream(stream), false); + return checkSum; + } + finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException ignore) { + // Do nothing + } + } + } + } } diff --git a/liquibase-standard/src/main/java/liquibase/changelog/ChangeLogIterator.java b/liquibase-standard/src/main/java/liquibase/changelog/ChangeLogIterator.java index 8019a24f295..cb1747741bc 100644 --- a/liquibase-standard/src/main/java/liquibase/changelog/ChangeLogIterator.java +++ b/liquibase-standard/src/main/java/liquibase/changelog/ChangeLogIterator.java @@ -14,8 +14,6 @@ import liquibase.exception.ValidationErrors; import liquibase.executor.Executor; import liquibase.executor.ExecutorService; -import liquibase.logging.core.BufferedLogService; -import liquibase.logging.core.CompositeLogService; import liquibase.util.StringUtil; import java.util.*; diff --git a/liquibase-standard/src/main/java/liquibase/changelog/ChangeSet.java b/liquibase-standard/src/main/java/liquibase/changelog/ChangeSet.java index 34993123d80..8990c510ff3 100644 --- a/liquibase-standard/src/main/java/liquibase/changelog/ChangeSet.java +++ b/liquibase-standard/src/main/java/liquibase/changelog/ChangeSet.java @@ -705,21 +705,19 @@ public ExecType execute(DatabaseChangeLog databaseChangeLog, ChangeExecListener log.fine("Reading ChangeSet: " + this); 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 may change each run and therefore is possibly incorrect and/or invalid:"); - } + if (listener != null) { + listener.willRun(change, this, changeLog, database); + } + if (change.generateStatementsVolatile(database)) { + executor.comment("WARNING The following SQL may change each run and therefore is possibly incorrect and/or invalid:"); + } - addSqlMdc(change, database, false); + addSqlMdc(change, database, false); - database.executeStatements(change, databaseChangeLog, sqlVisitors); - log.info(change.getConfirmationMessage()); - if (listener != null) { - listener.ran(change, this, changeLog, database); - } + database.executeStatements(change, databaseChangeLog, sqlVisitors); + log.info(change.getConfirmationMessage()); + if (listener != null) { + listener.ran(change, this, changeLog, database); } else { log.fine("Change " + change.getSerializedObjectName() + " not included for database " + database.getShortName()); } @@ -952,6 +950,14 @@ public List getChanges() { return Collections.unmodifiableList(changes); } + /** + * Method created to remove changes from a changeset + * @param collection + */ + public void removeAllChanges(Collection collection) { + this.changes.removeAll(collection); + } + public void addChange(Change change) { if (change == null) { return; diff --git a/liquibase-standard/src/main/java/liquibase/changelog/DatabaseChangeLog.java b/liquibase-standard/src/main/java/liquibase/changelog/DatabaseChangeLog.java index dc072aa867c..925e96aaf19 100644 --- a/liquibase-standard/src/main/java/liquibase/changelog/DatabaseChangeLog.java +++ b/liquibase-standard/src/main/java/liquibase/changelog/DatabaseChangeLog.java @@ -364,7 +364,9 @@ public ChangeSet getChangeSet(RanChangeSet ranChangeSet) { } public List getChangeSets(RanChangeSet ranChangeSet) { - return getChangeSets(ranChangeSet.getChangeLog(), ranChangeSet.getAuthor(), ranChangeSet.getId()); + List changesets = getChangeSets(ranChangeSet.getChangeLog(), ranChangeSet.getAuthor(), ranChangeSet.getId()); + changesets.forEach(c -> c.setStoredFilePath(ranChangeSet.getStoredChangeLog())); + return changesets; } public void load(ParsedNode parsedNode, ResourceAccessor resourceAccessor) throws ParsedNodeException, SetupException { diff --git a/liquibase-standard/src/main/java/liquibase/changelog/PropertyExpandingStream.java b/liquibase-standard/src/main/java/liquibase/changelog/PropertyExpandingStream.java new file mode 100644 index 00000000000..31dd9118587 --- /dev/null +++ b/liquibase-standard/src/main/java/liquibase/changelog/PropertyExpandingStream.java @@ -0,0 +1,71 @@ +package liquibase.changelog; + +import liquibase.GlobalConfiguration; +import liquibase.exception.UnexpectedLiquibaseException; +import liquibase.util.StreamUtil; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +public class PropertyExpandingStream extends InputStream { + + private InputStream stream; + + /** + * This method will read the content of the given stream and make any parameter update on the content. For example, + * making a property replacement into procedure text read from the sql file. + * @param stream + * @return an updated {@link InputStream} if any replacement has been performed in the content of the original stream. + */ + public PropertyExpandingStream(ChangeSet changeSet, InputStream stream) { + try { + if (changeSet == null) { + this.stream = stream; + } + else { + Charset encoding = GlobalConfiguration.FILE_ENCODING.getCurrentValue(); + String streamContent = StreamUtil.readStreamAsString(stream, encoding.toString()); + ChangeLogParameters parameters = changeSet.getChangeLogParameters(); + if (parameters != null) { + streamContent = parameters.expandExpressions(streamContent, changeSet.getChangeLog()); + } + this.stream = new ByteArrayInputStream(streamContent.getBytes(encoding)); + } + } + catch (IOException e) { + throw new UnexpectedLiquibaseException(e); + } + } + + @Override + public int read() throws IOException { + return this.stream.read(); + } + + @Override + public int available() throws IOException { + return stream.available(); + } + + @Override + public boolean markSupported() { + return stream.markSupported(); + } + + @Override + public synchronized void mark(int readLimit) { + stream.mark(readLimit); + } + + @Override + public synchronized void reset() throws IOException { + stream.reset(); + } + + @Override + public void close() throws IOException { + stream.close(); + } +} diff --git a/liquibase-standard/src/main/java/liquibase/changelog/filter/DbmsChangeSetFilter.java b/liquibase-standard/src/main/java/liquibase/changelog/filter/DbmsChangeSetFilter.java index 4ed83100314..fe3ec2377a9 100644 --- a/liquibase-standard/src/main/java/liquibase/changelog/filter/DbmsChangeSetFilter.java +++ b/liquibase-standard/src/main/java/liquibase/changelog/filter/DbmsChangeSetFilter.java @@ -1,5 +1,7 @@ package liquibase.changelog.filter; +import liquibase.change.Change; +import liquibase.change.DbmsTargetedChange; import liquibase.changelog.ChangeSet; import liquibase.database.Database; import liquibase.database.DatabaseList; @@ -24,13 +26,21 @@ public ChangeSetFilterResult accepts(ChangeSet changeSet) { if (database == null) { return new ChangeSetFilterResult(true, "No database connection, cannot evaluate dbms attribute", this.getClass(), getMdcName(), getDisplayName()); } - List visitorsToRemove = new ArrayList<>(); + List visitorsToRemove = new ArrayList<>(); + List changesToRemove = new ArrayList<>(); for (SqlVisitor visitor : changeSet.getSqlVisitors()) { if (!DatabaseList.definitionMatches(visitor.getApplicableDbms(), database, true)) { visitorsToRemove.add(visitor); } } + for(Change change : changeSet.getChanges()){ + if (((change instanceof DbmsTargetedChange)) && !DatabaseList.definitionMatches(((DbmsTargetedChange) change).getDbms(), database, true)){ + changesToRemove.add(change); + } + } + changeSet.getSqlVisitors().removeAll(visitorsToRemove); + changeSet.removeAllChanges(changesToRemove); String dbmsList; if ((changeSet.getDbmsSet() == null) || changeSet.getDbmsSet().isEmpty()) { diff --git a/liquibase-standard/src/main/java/liquibase/changelog/filter/ShouldRunChangeSetFilter.java b/liquibase-standard/src/main/java/liquibase/changelog/filter/ShouldRunChangeSetFilter.java index 23ae7e5c5d4..2f2b3402bc6 100644 --- a/liquibase-standard/src/main/java/liquibase/changelog/filter/ShouldRunChangeSetFilter.java +++ b/liquibase-standard/src/main/java/liquibase/changelog/filter/ShouldRunChangeSetFilter.java @@ -70,6 +70,9 @@ public ChangeSetFilterResult accepts(ChangeSet changeSet) { protected boolean checksumChanged(ChangeSet changeSet, RanChangeSet ranChangeSet) { + if (ranChangeSet.getLastCheckSum() == null) { + return false; + } return !changeSet.generateCheckSum().equals(ranChangeSet.getLastCheckSum()); } diff --git a/liquibase-standard/src/main/java/liquibase/command/core/ReleaseLocksCommandStep.java b/liquibase-standard/src/main/java/liquibase/command/core/ReleaseLocksCommandStep.java index 91f500c2bc6..e954e2f6757 100644 --- a/liquibase-standard/src/main/java/liquibase/command/core/ReleaseLocksCommandStep.java +++ b/liquibase-standard/src/main/java/liquibase/command/core/ReleaseLocksCommandStep.java @@ -1,5 +1,7 @@ package liquibase.command.core; +import liquibase.Contexts; +import liquibase.LabelExpression; import liquibase.Scope; import liquibase.command.AbstractCommandStep; import liquibase.command.CommandDefinition; @@ -17,6 +19,7 @@ public class ReleaseLocksCommandStep extends AbstractCommandStep { @Override public void run(CommandResultsBuilder resultsBuilder) throws Exception { Database database = (Database) resultsBuilder.getCommandScope().getDependency(Database.class); + ListLocksCommandStep.checkLiquibaseTables(false, null, new Contexts(), new LabelExpression(), database); LockServiceFactory.getInstance().getLockService(database).forceReleaseLock(); Scope.getCurrentScope().getUI().sendMessage(String.format( coreBundle.getString("successfully.released.database.change.log.locks"), diff --git a/liquibase-standard/src/main/java/liquibase/sql/visitor/AbstractSqlVisitor.java b/liquibase-standard/src/main/java/liquibase/sql/visitor/AbstractSqlVisitor.java index eec78fd6ef2..a47234e87bd 100644 --- a/liquibase-standard/src/main/java/liquibase/sql/visitor/AbstractSqlVisitor.java +++ b/liquibase-standard/src/main/java/liquibase/sql/visitor/AbstractSqlVisitor.java @@ -11,6 +11,7 @@ import liquibase.util.ObjectUtil; import liquibase.util.StringUtil; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -78,7 +79,23 @@ public void setLabels(Labels labels) { @Override public CheckSum generateCheckSum() { - return CheckSum.compute(new StringChangeLogSerializer().serialize(this, false)); + return CheckSum.compute(new StringChangeLogSerializer(new StringChangeLogSerializer.FieldFilter(){ + @Override + public boolean include(Object obj, String field, Object value) { + if(Arrays.stream(getExcludedFieldFilters()).anyMatch(filter -> filter.equals(field))) { + return false; + } + return super.include(obj, field, value); + } + }).serialize(this, false)); + } + + public String[] getExcludedFieldFilters() { + return new String[]{ + "applicableDbms", + "contextFilter", + "labels" + }; } @Override diff --git a/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/UpdateChangeSetChecksumGenerator.java b/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/UpdateChangeSetChecksumGenerator.java index 167dd4013e9..153a2459294 100644 --- a/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/UpdateChangeSetChecksumGenerator.java +++ b/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/UpdateChangeSetChecksumGenerator.java @@ -11,6 +11,7 @@ import liquibase.statement.SqlStatement; import liquibase.statement.core.UpdateChangeSetChecksumStatement; import liquibase.statement.core.UpdateStatement; +import liquibase.util.StringUtil; public class UpdateChangeSetChecksumGenerator extends AbstractSqlGenerator { @Override @@ -33,11 +34,18 @@ public Sql[] generateSql(UpdateChangeSetChecksumStatement statement, Database da .setWhereClause(database.escapeObjectName("ID", LiquibaseColumn.class) + " = ? " + "AND " + database.escapeObjectName("AUTHOR", LiquibaseColumn.class) + " = ? " + "AND " + database.escapeObjectName("FILENAME", LiquibaseColumn.class) + " = ?") - .addWhereParameters(changeSet.getId(), changeSet.getAuthor(), changeSet.getFilePath()); + .addWhereParameters(changeSet.getId(), changeSet.getAuthor(), this.getFilePath(changeSet)); return SqlGeneratorFactory.getInstance().generateSql(runStatement, database); } finally { database.setObjectQuotingStrategy(currentStrategy); } } -} \ No newline at end of file + + private String getFilePath(ChangeSet changeSet) { + if (StringUtil.isNotEmpty(changeSet.getStoredFilePath())) { + return changeSet.getStoredFilePath(); + } + return changeSet.getFilePath(); + } +} diff --git a/liquibase-standard/src/test/groovy/liquibase/change/AbstractChangeTest.groovy b/liquibase-standard/src/test/groovy/liquibase/change/AbstractChangeTest.groovy index bacbc855aaa..6676baa2d0c 100644 --- a/liquibase-standard/src/test/groovy/liquibase/change/AbstractChangeTest.groovy +++ b/liquibase-standard/src/test/groovy/liquibase/change/AbstractChangeTest.groovy @@ -1,8 +1,8 @@ package liquibase.change +import liquibase.ContextExpression +import liquibase.Labels import liquibase.change.core.CreateProcedureChange -import liquibase.change.core.CreateTableChange -import liquibase.change.core.CreateViewChange import liquibase.change.core.SQLFileChange import liquibase.changelog.ChangeSet import liquibase.changelog.DatabaseChangeLog @@ -343,6 +343,58 @@ class AbstractChangeTest extends Specification { "This/is/a/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/ver/long/test/change/path" | new CreateProcedureChange() } + def "context filter is not considered on checksum generation"() { + when: + ChangeSet originalChange = new ChangeSet("testId", "testAuthor", false, false, "path/changelog", null, null, null) + ChangeSet changeWithContext = originalChange + + CheckSum changeWithoutContextCheckSum = originalChange.generateCheckSum() + changeWithContext.setContextFilter(new ContextExpression("test")) + CheckSum changeWithContextCheckSum = changeWithContext.generateCheckSum() + + then: + changeWithoutContextCheckSum == changeWithContextCheckSum + } + + def "label is not considered on checksum generation"() { + when: + ChangeSet originalChange = new ChangeSet("testId", "testAuthor", false, false, "path/changelog", null, null, null) + ChangeSet changeWithLabel = originalChange + + CheckSum changeWithoutLabelCheckSum = originalChange.generateCheckSum() + changeWithLabel.setLabels(new Labels("test")) + CheckSum changeWithLabelCheckSum = changeWithLabel.generateCheckSum() + + then: + changeWithoutLabelCheckSum == changeWithLabelCheckSum + } + + def "dbms is not considered on checksum generation"() { + when: + ChangeSet originalChange = new ChangeSet("testId", "testAuthor", false, false, "path/changelog", null, null, null) + ChangeSet changeWithDbms = originalChange + + CheckSum changeWithoutDbmsCheckSum = originalChange.generateCheckSum() + changeWithDbms.setDbms("postgresql") + CheckSum changeWithDbmsCheckSum = changeWithDbms.generateCheckSum() + + then: + changeWithoutDbmsCheckSum == changeWithDbmsCheckSum + } + + def "comment is not considered on checksum generation"() { + when: + ChangeSet originalChange = new ChangeSet("testId", "testAuthor", false, false, "path/changelog", null, null, null) + ChangeSet changeWithComment = originalChange + + CheckSum changeWithoutCommentCheckSum = originalChange.generateCheckSum() + changeWithComment.setComments("This is a test comment") + CheckSum changeWithCommentCheckSum = changeWithComment.generateCheckSum() + + then: + changeWithoutCommentCheckSum == changeWithCommentCheckSum + } + @DatabaseChange(name = "exampleParamelessAbstractChange", description = "Used for the AbstractChangeTest unit test", priority = 1) private static class ExampleParamlessAbstractChange extends AbstractChange { diff --git a/liquibase-standard/src/test/groovy/liquibase/change/core/CreateProcedureChangeTest.groovy b/liquibase-standard/src/test/groovy/liquibase/change/core/CreateProcedureChangeTest.groovy index 0af58163242..3f876cdcccc 100644 --- a/liquibase-standard/src/test/groovy/liquibase/change/core/CreateProcedureChangeTest.groovy +++ b/liquibase-standard/src/test/groovy/liquibase/change/core/CreateProcedureChangeTest.groovy @@ -1,10 +1,10 @@ package liquibase.change.core import liquibase.Scope +import liquibase.change.CheckSum import liquibase.change.StandardChangeTest import liquibase.changelog.ChangeSet import liquibase.changelog.DatabaseChangeLog -import liquibase.database.core.MSSQLDatabase import liquibase.database.core.OracleDatabase import liquibase.database.core.PostgresDatabase import liquibase.exception.ValidationErrors @@ -13,12 +13,11 @@ import liquibase.database.core.MockDatabase import liquibase.sdk.resource.MockResourceAccessor import liquibase.snapshot.MockSnapshotGeneratorFactory import liquibase.snapshot.SnapshotGeneratorFactory -import liquibase.sqlgenerator.core.CreateProcedureGenerator import liquibase.test.JUnitResourceAccessor import liquibase.util.StreamUtil import spock.lang.Unroll -public class CreateProcedureChangeTest extends StandardChangeTest { +class CreateProcedureChangeTest extends StandardChangeTest { def getConfirmationMessage() throws Exception { when: @@ -28,6 +27,8 @@ public class CreateProcedureChangeTest extends StandardChangeTest { "Stored procedure created" == refactoring.getConfirmationMessage() } + public static final String PROCEDURE_TEXT = "SOME SQL"; + def "checkStatus"() { when: def database = new MockDatabase() @@ -47,7 +48,7 @@ public class CreateProcedureChangeTest extends StandardChangeTest { change.validate(new OracleDatabase()) then: - change.serialize().toString() == "createProcedure[procedureBody=create procedure sql]" + change.serialize().toString() == "createProcedure[procedureText=create procedure sql]" } @Unroll @@ -70,7 +71,7 @@ public class CreateProcedureChangeTest extends StandardChangeTest { fileContents.trim() == "My Logic Here" where: - sqlPath | logicalFilePath | relativeToChangelogFile + sqlPath | logicalFilePath | relativeToChangelogFile "com/example/my-logic.sql" | null | false "com/example/my-logic.sql" | "a/logical/path.xml" | false "my-logic.sql" | null | true @@ -90,11 +91,144 @@ public class CreateProcedureChangeTest extends StandardChangeTest { valErrors.getErrorMessages().get(0).contains(expectedValidationErrorMsg); where: - database | dbms | expectedValidationErrorMsg + database | dbms | expectedValidationErrorMsg new PostgresDatabase() | "post" | String.format("%s is not a supported DB", dbms) new PostgresDatabase() | "postgresql" | "" new MockDatabase() | "postgresql, h2, mssql, !sqlite" | "" new PostgresDatabase() | "none" | "" new PostgresDatabase() | "all" | "" } + + def "dbms is not considered on checksum generation"() { + when: + CreateProcedureChange change = new CreateProcedureChange() + change.setProcedureText(PROCEDURE_TEXT) + CheckSum procedureCheckSumWithoutDbms = change.generateCheckSum() + CreateProcedureChange change2 = new CreateProcedureChange() + change2.setProcedureText(PROCEDURE_TEXT) + change2.setDbms("postgresql") + CheckSum procedureCheckSumWithDbms = change2.generateCheckSum() + + then: + procedureCheckSumWithoutDbms == procedureCheckSumWithDbms + + } + + def "path is not considered on checksum generation"() { + when: + String testScopeId = Scope.enter([ + "resourceAccessor": new MockResourceAccessor([ + "test.sql": PROCEDURE_TEXT + ]) + ]) + + CreateProcedureChange change = new CreateProcedureChange() + change.setProcedureText(PROCEDURE_TEXT) + CheckSum procedureCheckSumWithoutPath = change.generateCheckSum() + CreateProcedureChange change2 = new CreateProcedureChange() + change2.setPath("test.sql") + //Below check sum generation should not take path property into account + CheckSum procedureCheckSumWithPath = change2.generateCheckSum() + //TODO: Move this Scope.exit() call into a cleanUpSpec method + Scope.exit(testScopeId) + + then: + procedureCheckSumWithoutPath == procedureCheckSumWithPath + } + + def "comment is not considered on checksum generation"() { + when: + CreateProcedureChange change = new CreateProcedureChange() + change.setProcedureText(PROCEDURE_TEXT) + CheckSum procedureCheckSumWithoutComments = change.generateCheckSum() + CreateProcedureChange change2 = new CreateProcedureChange() + change2.setProcedureText(PROCEDURE_TEXT) + change2.setComments("This is a test") + CheckSum procedureCheckSumWithComments = change2.generateCheckSum() + + then: + procedureCheckSumWithoutComments == procedureCheckSumWithComments + } + + def "encoding is not considered on checksum generation"() { + when: + CreateProcedureChange change = new CreateProcedureChange() + change.setProcedureText(PROCEDURE_TEXT) + CheckSum procedureCheckSumWithoutEncoding = change.generateCheckSum() + CreateProcedureChange change2 = new CreateProcedureChange() + change2.setProcedureText(PROCEDURE_TEXT) + change2.setEncoding("UTF-8") + CheckSum procedureCheckSumWithEncoding = change2.generateCheckSum() + + then: + procedureCheckSumWithoutEncoding == procedureCheckSumWithEncoding + } + + def "procedure text updated with whitespaces should not compute a new checksum"() { + when: + CreateProcedureChange change = new CreateProcedureChange() + change.setProcedureText(PROCEDURE_TEXT) + CheckSum procedureTextCheckSum = change.generateCheckSum() + CreateProcedureChange change2 = new CreateProcedureChange() + change2.setProcedureText(PROCEDURE_TEXT.concat(" \n")) + CheckSum procedureTextModifiedCheckSum = change2.generateCheckSum() + + then: + procedureTextCheckSum == procedureTextModifiedCheckSum + } + + def "checksum gets updated having a change on procedure text"() { + when: + CreateProcedureChange change = new CreateProcedureChange() + change.setProcedureText(PROCEDURE_TEXT) + CheckSum procedureTextOriginalCheckSum = change.generateCheckSum() + + StringBuilder procedureTextUpdated = new StringBuilder(PROCEDURE_TEXT) + procedureTextUpdated.append(" WHERE 1=1") + change.setProcedureText(procedureTextUpdated.toString()) + CheckSum procedureTextUpdatedCheckSum = change.generateCheckSum() + + then: + procedureTextOriginalCheckSum != procedureTextUpdatedCheckSum + } + + def "validate checksum gets re-computed if procedure text gets updated "() { + when: + String procedureText = + """CREATE OR REPLACE PROCEDURE testHello() + LANGUAGE plpgsql + AS \$\$ + BEGIN + raise notice 'valueToReplace'; + END \$\$""" + + def change = new CreateProcedureChange(); + procedureText = procedureText.replace("valueToReplace", "value1") + change.setProcedureText(procedureText) + + def checkSumFirstReplacement = change.generateCheckSum().toString() + + procedureText = procedureText.replace("value1", "value2") + change.setProcedureText(procedureText) + + def checkSumSecondReplacement = change.generateCheckSum().toString() + + then: + checkSumFirstReplacement != checkSumSecondReplacement + } + + def "relativeToChangelogFile attribute is not considered on checksum generation"() { + when: + CreateProcedureChange changeWithoutRelativeToChangelogFileAttribSet = new CreateProcedureChange() + changeWithoutRelativeToChangelogFileAttribSet.setProcedureText(PROCEDURE_TEXT) + CheckSum changeWithoutRelativeToChangelogFileAttribSetCheckSum = changeWithoutRelativeToChangelogFileAttribSet.generateCheckSum() + + CreateProcedureChange changeWithRelativeToChangelogFileAttribSet = new CreateProcedureChange() + changeWithRelativeToChangelogFileAttribSet.setProcedureText(PROCEDURE_TEXT) + changeWithRelativeToChangelogFileAttribSet.setRelativeToChangelogFile(true) + CheckSum changeWithRelativeToChangelogFileAttribSetCheckSum = changeWithRelativeToChangelogFileAttribSet.generateCheckSum() + + then: + changeWithoutRelativeToChangelogFileAttribSetCheckSum == changeWithRelativeToChangelogFileAttribSetCheckSum + } } diff --git a/liquibase-standard/src/test/groovy/liquibase/change/core/CreateViewChangeTest.groovy b/liquibase-standard/src/test/groovy/liquibase/change/core/CreateViewChangeTest.groovy index dd4f78537aa..bc0a2a6600f 100644 --- a/liquibase-standard/src/test/groovy/liquibase/change/core/CreateViewChangeTest.groovy +++ b/liquibase-standard/src/test/groovy/liquibase/change/core/CreateViewChangeTest.groovy @@ -2,12 +2,14 @@ package liquibase.change.core import liquibase.Scope import liquibase.change.ChangeStatus +import liquibase.change.CheckSum import liquibase.change.StandardChangeTest import liquibase.changelog.ChangeSet import liquibase.changelog.DatabaseChangeLog import liquibase.database.core.MockDatabase import liquibase.exception.SetupException import liquibase.parser.core.ParsedNodeException +import liquibase.sdk.resource.MockResourceAccessor import liquibase.snapshot.MockSnapshotGeneratorFactory import liquibase.snapshot.SnapshotGeneratorFactory import liquibase.structure.core.View @@ -15,8 +17,9 @@ import liquibase.test.JUnitResourceAccessor import liquibase.util.StreamUtil import spock.lang.Unroll -public class CreateViewChangeTest extends StandardChangeTest { +class CreateViewChangeTest extends StandardChangeTest { + public static final String SELECT_QUERY = "SELECT * FROM TestTable"; def getConfirmationMessage() throws Exception { when: @@ -88,6 +91,102 @@ public class CreateViewChangeTest extends StandardChangeTest { "com/example/my-logic.sql" | "a/logical/path.xml" | false "my-logic.sql" | null | true "my-logic.sql" | "a/logical/path.xml" | true + } + + def "path is not considered on checksum generation"() { + when: + String testScopeId = Scope.enter([ + "resourceAccessor": new MockResourceAccessor([ + "viewTest.sql": SELECT_QUERY + ]) + ]) + + CreateViewChange change = new CreateViewChange() + change.setSelectQuery(SELECT_QUERY) + CheckSum viewCheckSumWithoutPath = change.generateCheckSum() + CreateViewChange change2 = new CreateViewChange() + change2.setPath("viewTest.sql") + CheckSum viewCheckSumWithPath = change2.generateCheckSum() + //TODO: Move this Scope.exit() call into a cleanUpSpec method + Scope.exit(testScopeId) + + then: + viewCheckSumWithoutPath == viewCheckSumWithPath + } + + def "encoding is not considered on checksum generation"() { + when: + CreateViewChange change = new CreateViewChange() + change.setSelectQuery(SELECT_QUERY) + CheckSum viewCheckSumWithoutEncoding = change.generateCheckSum() + CreateViewChange change2 = new CreateViewChange() + change2.setSelectQuery(SELECT_QUERY) + change2.setEncoding("UTF-8") + CheckSum viewCheckSumWithEncoding = change2.generateCheckSum() + + then: + viewCheckSumWithoutEncoding == viewCheckSumWithEncoding + } + + def "select query updated with whitespaces should not be computed as a new checksum"() { + when: + CreateViewChange change = new CreateViewChange() + change.setSelectQuery(SELECT_QUERY) + CheckSum viewTextCheckSum = change.generateCheckSum() + CreateViewChange change2 = new CreateViewChange() + change2.setSelectQuery(SELECT_QUERY.concat(" \n")) + CheckSum viewTextModifiedCheckSum = change2.generateCheckSum() + + then: + viewTextCheckSum == viewTextModifiedCheckSum + } + + def "checksum gets updated having a change on select query"() { + when: + CreateViewChange change = new CreateViewChange() + change.setSelectQuery(SELECT_QUERY) + CheckSum viewTextOriginalCheckSum = change.generateCheckSum() + StringBuilder selectQueryUpdated = new StringBuilder(SELECT_QUERY) + selectQueryUpdated.append(" WHERE 1=1") + change.setSelectQuery(selectQueryUpdated.toString()) + CheckSum viewTextUpdatedCheckSum = change.generateCheckSum(); + + then: + viewTextOriginalCheckSum != viewTextUpdatedCheckSum + } + + def "validate checksum gets re-computed if select query text gets updated"() { + when: + String selectQueryText = "SELECT id, name FROM person WHERE id > valueToReplace;" + + selectQueryText = selectQueryText.replace("valueToReplace", "value1") + def change = new CreateViewChange(); + change.setSelectQuery(selectQueryText) + + def checkSumFirstReplacement = change.generateCheckSum().toString() + + selectQueryText = selectQueryText.replace("value1", "value2") + change.setSelectQuery(selectQueryText) + + def checkSumSecondReplacement = change.generateCheckSum().toString() + + then: + checkSumFirstReplacement != checkSumSecondReplacement + } + + def "relativeToChangelogFile attribute is not considered on checksum generation"() { + when: + CreateViewChange changeWithoutRelativeToChangelogFileAttribSet = new CreateViewChange() + changeWithoutRelativeToChangelogFileAttribSet.setSelectQuery(SELECT_QUERY) + CheckSum changeWithoutRelativeToChangelogFileAttribSetCheckSum = changeWithoutRelativeToChangelogFileAttribSet.generateCheckSum() + + CreateViewChange changeWithRelativeToChangelogFileAttribSet = new CreateViewChange() + changeWithRelativeToChangelogFileAttribSet.setSelectQuery(SELECT_QUERY) + changeWithRelativeToChangelogFileAttribSet.setRelativeToChangelogFile(true) + CheckSum changeWithRelativeToChangelogFileAttribSetCheckSum = changeWithRelativeToChangelogFileAttribSet.generateCheckSum() + + then: + changeWithoutRelativeToChangelogFileAttribSetCheckSum == changeWithRelativeToChangelogFileAttribSetCheckSum } } diff --git a/liquibase-standard/src/test/groovy/liquibase/change/core/SQLFileChangeTest.groovy b/liquibase-standard/src/test/groovy/liquibase/change/core/SQLFileChangeTest.groovy index 45398a50619..57a2857fcaf 100644 --- a/liquibase-standard/src/test/groovy/liquibase/change/core/SQLFileChangeTest.groovy +++ b/liquibase-standard/src/test/groovy/liquibase/change/core/SQLFileChangeTest.groovy @@ -16,7 +16,7 @@ import spock.lang.Unroll import static org.junit.Assert.assertEquals -public class SQLFileChangeTest extends StandardChangeTest { +class SQLFileChangeTest extends StandardChangeTest { def "generateStatements throws Exception if file does not exist"() throws Exception { when: @@ -142,4 +142,28 @@ public class SQLFileChangeTest extends StandardChangeTest { } + def "validate checksum gets re-computed if sql(file) content change"() { + when: + String procedureText = + """CREATE OR REPLACE PROCEDURE testHello() + LANGUAGE plpgsql + AS \$\$ + BEGIN + raise notice 'valueToReplace'; + END \$\$""" + def change = new SQLFileChange() + + procedureText = procedureText.replace("valueToReplace", "value1") + change.setSql(procedureText) + + def checkSumFirstReplacement = change.generateCheckSum().toString() + + procedureText = procedureText.replace("value1", "value2") + change.setSql(procedureText) + + def checkSumSecondReplacement = change.generateCheckSum().toString() + + then: + checkSumFirstReplacement != checkSumSecondReplacement + } } diff --git a/liquibase-standard/src/test/groovy/liquibase/changelog/filter/ShouldRunChangeSetFilterTest.groovy b/liquibase-standard/src/test/groovy/liquibase/changelog/filter/ShouldRunChangeSetFilterTest.groovy index 0222b36bada..2f9ba4a66c1 100644 --- a/liquibase-standard/src/test/groovy/liquibase/changelog/filter/ShouldRunChangeSetFilterTest.groovy +++ b/liquibase-standard/src/test/groovy/liquibase/changelog/filter/ShouldRunChangeSetFilterTest.groovy @@ -14,7 +14,7 @@ import spock.lang.Specification import static org.junit.Assert.assertFalse import static org.junit.Assert.assertTrue -public class ShouldRunChangeSetFilterTest extends Specification { +class ShouldRunChangeSetFilterTest extends Specification { Database database; @@ -22,7 +22,7 @@ public class ShouldRunChangeSetFilterTest extends Specification { database = Mock(Database.class); } - public void accepts_noneRun() throws DatabaseException { + void accepts_noneRun() throws DatabaseException { when: database.getRanChangeSetList() >> new ArrayList() @@ -32,7 +32,7 @@ public class ShouldRunChangeSetFilterTest extends Specification { assertTrue(filter.accepts(new ChangeSet("1", "testAuthor", false, false, "path/changelog", null, null, null)).isAccepted()); } - public void accepts() throws DatabaseException { + void accepts() throws DatabaseException { when: given_a_database_with_two_executed_changesets(); ShouldRunChangeSetFilter filter = new ShouldRunChangeSetFilter(database); @@ -46,7 +46,7 @@ public class ShouldRunChangeSetFilterTest extends Specification { assertTrue("ChangSet with different path should be accepted", filter.accepts(new ChangeSet("1", "testAuthor", false, false, "other/changelog", null, null, null)).isAccepted()); } - public void does_NOT_accept_current_changeset_with_classpath_prefix() throws DatabaseException { + void does_NOT_accept_current_changeset_with_classpath_prefix() throws DatabaseException { when: given_a_database_with_two_executed_changesets(); ChangeSet changeSetWithClasspathPrefix = new ChangeSet("1", "testAuthor", false, false, "classpath:path/changelog", null, null, null); @@ -57,7 +57,7 @@ public class ShouldRunChangeSetFilterTest extends Specification { assertFalse(filter.accepts(changeSetWithClasspathPrefix).isAccepted()); } - public void does_NOT_accept_current_changeset_when_inserted_changeset_has_classpath_prefix() throws DatabaseException { + void does_NOT_accept_current_changeset_when_inserted_changeset_has_classpath_prefix() throws DatabaseException { when: given_a_database_with_two_executed_changesets(); ChangeSet changeSet = new ChangeSet("2", "testAuthor", false, false, "path/changelog", null, null, null); @@ -68,7 +68,7 @@ public class ShouldRunChangeSetFilterTest extends Specification { assertFalse(filter.accepts(changeSet).isAccepted()); } - public void does_NOT_accept_current_changeset_when_both_have_classpath_prefix() throws DatabaseException { + void does_NOT_accept_current_changeset_when_both_have_classpath_prefix() throws DatabaseException { when: given_a_database_with_two_executed_changesets(); ChangeSet changeSet = new ChangeSet("2", "testAuthor", false, false, "classpath:path/changelog", null, null, null); @@ -119,7 +119,7 @@ public class ShouldRunChangeSetFilterTest extends Specification { RanChangeSet ranChangeSet1 = new RanChangeSet("path/changelog", "1", "testAuthor", CheckSum.parse("not_matched_checksum"), new Date(100000), null, null, null, null, null, null, null) ranChangeSet1.setOrderExecuted(1) ranChanges.add(ranChangeSet1) - RanChangeSet ranChangeSet2 = new RanChangeSet("path/changelog", "1", "testAuthor", CheckSum.parse("8:d41d8cd98f00b204e9800998ecf8427e"), new Date(100000 + 5), null, null, null, null, null, null, null) + RanChangeSet ranChangeSet2 = new RanChangeSet("path/changelog", "1", "testAuthor", CheckSum.parse("9:d41d8cd98f00b204e9800998ecf8427e"), new Date(100000 + 5), null, null, null, null, null, null, null) ranChangeSet2.setOrderExecuted(2) ranChanges.add(ranChangeSet2) @@ -127,7 +127,7 @@ public class ShouldRunChangeSetFilterTest extends Specification { } - public void should_decline_not_changed_changeset_when_has_run_on_change() throws DatabaseException { + void should_decline_not_changed_changeset_when_has_run_on_change() throws DatabaseException { when: given_a_database_with_one_twice_executed_changeset() ShouldRunChangeSetFilter filter = new ShouldRunChangeSetFilter(database) diff --git a/liquibase-standard/src/test/groovy/liquibase/hub/model/HubChangeTest.groovy b/liquibase-standard/src/test/groovy/liquibase/hub/model/HubChangeTest.groovy index cf47bd7d2a4..a06821e55f2 100644 --- a/liquibase-standard/src/test/groovy/liquibase/hub/model/HubChangeTest.groovy +++ b/liquibase-standard/src/test/groovy/liquibase/hub/model/HubChangeTest.groovy @@ -105,7 +105,7 @@ class HubChangeTest extends Specification { hubChange.getContexts() == CONTEXT_LIST hubChange.getDescription() == "empty" // Checksum is generated automatically in the getter even if it is null - hubChange.getMd5sum() == "8:d41d8cd98f00b204e9800998ecf8427e" + hubChange.getMd5sum() == "9:d41d8cd98f00b204e9800998ecf8427e" // Constants hubChange.getOrderExecuted() == 0 hubChange.getExecType() == "EXECUTED" @@ -132,7 +132,7 @@ class HubChangeTest extends Specification { // Default value returned from ContextExpression.toString() if no contexts hubChange.getContexts() == "()" // Checksum is generated automatically in the getter even if it is null - hubChange.getMd5sum() == "8:d41d8cd98f00b204e9800998ecf8427e" + hubChange.getMd5sum() == "9:d41d8cd98f00b204e9800998ecf8427e" // Constants hubChange.getOrderExecuted() == 0 hubChange.getExecType() == "EXECUTED" diff --git a/liquibase-standard/src/test/java/liquibase/change/AbstractSQLChangeTest.java b/liquibase-standard/src/test/java/liquibase/change/AbstractSQLChangeTest.java index d58421966be..4794bde4700 100644 --- a/liquibase-standard/src/test/java/liquibase/change/AbstractSQLChangeTest.java +++ b/liquibase-standard/src/test/java/liquibase/change/AbstractSQLChangeTest.java @@ -71,7 +71,7 @@ public void setSql() { } @Test - public void setEndDelmiter() { + public void setEndDelimiter() { AbstractSQLChange change = new ExampleAbstractSQLChange(); change.setEndDelimiter("GO"); @@ -104,15 +104,15 @@ public void generateCheckSum_changesBasedOnParams() { ExampleAbstractSQLChange change = new ExampleAbstractSQLChange("SOME SQL"); change.setSplitStatements(false); - assertNotEquals(baseCheckSum.toString(), change.generateCheckSum().toString()); + assertEquals(baseCheckSum.toString(), change.generateCheckSum().toString()); change = new ExampleAbstractSQLChange("SOME SQL"); change.setEndDelimiter("X"); - assertNotEquals(baseCheckSum.toString(), change.generateCheckSum().toString()); + assertEquals(baseCheckSum.toString(), change.generateCheckSum().toString()); change = new ExampleAbstractSQLChange("SOME SQL"); change.setStripComments(true); - assertNotEquals(baseCheckSum.toString(), change.generateCheckSum().toString()); + assertEquals(baseCheckSum.toString(), change.generateCheckSum().toString()); } // @Test @@ -194,21 +194,21 @@ public void generateStatements_convertsEndingsOnSqlServer() { @Test public void normalizeSql() throws IOException { - assertNormalizingStreamCorrect("single line String", "single line String"); - assertNormalizingStreamCorrect("single line string with whitespace", "single line string with whitespace"); - assertNormalizingStreamCorrect("multiple line string", "\r\nmultiple\r\nline\r\nstring\r\n"); - assertNormalizingStreamCorrect("multiple line string", "\rmultiple\rline\rstring\r"); - assertNormalizingStreamCorrect("multiple line string", "\nmultiple\nline\nstring\n"); - assertNormalizingStreamCorrect("a line with double newlines", "\n\na\nline \n with \r\n \r\n double \n \n \n \n newlines"); + assertNormalizingStreamCorrect("singlelineString", "single line String"); + assertNormalizingStreamCorrect("singlelinestringwithwhitespace", "single line string with whitespace"); + assertNormalizingStreamCorrect("multiplelinestring", "\r\nmultiple\r\nline\r\nstring\r\n"); + assertNormalizingStreamCorrect("multiplelinestring", "\rmultiple\rline\rstring\r"); + assertNormalizingStreamCorrect("multiplelinestring", "\nmultiple\nline\nstring\n"); + assertNormalizingStreamCorrect("alinewithdoublenewlines", "\n\na\nline \n with \r\n \r\n double \n \n \n \n newlines"); // assertNormalizingStreamCorrect("", null); assertNormalizingStreamCorrect("", " "); assertNormalizingStreamCorrect("", " \n \n \n \n "); //test quickBuffer -> resizingBuffer handoff String longSpaceString = "a line with a lot of: wait for it.... spaces"; - assertNormalizingStreamCorrect("a line with a lot of: wait for it.... spaces", longSpaceString); + assertNormalizingStreamCorrect("alinewithalotof:waitforit....spaces", longSpaceString); - String versionNormalized = "INSERT INTO recommendation_list(instanceId, name, publicId) SELECT DISTINCT instanceId, \"default\" as name, \"default\" as publicId FROM recommendation;"; + String versionNormalized = "INSERTINTOrecommendation_list(instanceId,name,publicId)SELECTDISTINCTinstanceId,\"default\"asname,\"default\"aspublicIdFROMrecommendation;"; String version1 = "INSERT INTO recommendation_list(instanceId, name, publicId)\n" + "SELECT DISTINCT instanceId, \"default\" as name, \"default\" as publicId\n" + @@ -228,8 +228,8 @@ public void normalizeSql() throws IOException { } private void assertNormalizingStreamCorrect(String expected, String toCorrect) throws IOException { - AbstractSQLChange.NormalizingStream normalizingStream = new AbstractSQLChange.NormalizingStream("x", true, false, new ByteArrayInputStream(toCorrect.getBytes())); - assertEquals("x:true:false:"+expected, StreamUtil.readStreamAsString(normalizingStream)); + AbstractSQLChange.NormalizingStream normalizingStream = new AbstractSQLChange.NormalizingStream(new ByteArrayInputStream(toCorrect.getBytes())); + assertEquals(expected, StreamUtil.readStreamAsString(normalizingStream)); } // @Test diff --git a/liquibase-standard/src/test/java/liquibase/change/CheckSumTest.java b/liquibase-standard/src/test/java/liquibase/change/CheckSumTest.java index 5ba7d81446c..94051f49658 100644 --- a/liquibase-standard/src/test/java/liquibase/change/CheckSumTest.java +++ b/liquibase-standard/src/test/java/liquibase/change/CheckSumTest.java @@ -31,7 +31,7 @@ public void parse_v1() { @Test public void getCurrentVersion() { - assertEquals(8, CheckSum.getCurrentVersion()); + assertEquals(9, CheckSum.getCurrentVersion()); } @Test diff --git a/liquibase-standard/src/test/java/liquibase/sqlgenerator/core/UpdateChangeSetChecksumGeneratorTest.java b/liquibase-standard/src/test/java/liquibase/sqlgenerator/core/UpdateChangeSetChecksumGeneratorTest.java new file mode 100644 index 00000000000..382014c9ce9 --- /dev/null +++ b/liquibase-standard/src/test/java/liquibase/sqlgenerator/core/UpdateChangeSetChecksumGeneratorTest.java @@ -0,0 +1,97 @@ +package liquibase.sqlgenerator.core; + +import liquibase.change.CheckSum; +import liquibase.changelog.ChangeSet; +import liquibase.database.Database; +import liquibase.sqlgenerator.SqlGeneratorFactory; +import liquibase.statement.SqlStatement; +import liquibase.statement.core.UpdateChangeSetChecksumStatement; +import liquibase.statement.core.UpdateStatement; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class UpdateChangeSetChecksumGeneratorTest { + + public static final String SOME_UPDATED_CHECK_SUM = "SomeUpdatedCheckSum"; + public static final String CHANGESET_SOME_ID = "SomeId"; + public static final String CHANGESET_SOME_AUTHOR = "SomeAuthor"; + public static final String CHANGESET_NORMALIZED_FILE_PATH = "SomeNormalizedFilePath"; + public static final String CHANGESET_STORED_FILE_PATH = "SomeStoredFilePath"; + + UpdateChangeSetChecksumGenerator sqlGenerator = new UpdateChangeSetChecksumGenerator(); + + @Mock + Database mockedDatabase; + + @Mock + ChangeSet mockedChangeSet; + + @Mock + CheckSum mockedUpdatedCheckSum; + + @Mock + SqlGeneratorFactory sqlGeneratorFactory; + + @Before + public void setUp() throws Exception { + doReturn("SomeCatalogName").when(mockedDatabase).getLiquibaseCatalogName(); + doReturn("SomeSchemaName").when(mockedDatabase).getLiquibaseSchemaName(); + doReturn("SomeChangeLogTableName").when(mockedDatabase).getDatabaseChangeLogTableName(); + doReturn(SOME_UPDATED_CHECK_SUM).when(mockedUpdatedCheckSum).toString(); + doReturn(mockedUpdatedCheckSum).when(mockedChangeSet).generateCheckSum(); + doReturn(CHANGESET_SOME_ID).when(mockedChangeSet).getId(); + doReturn(CHANGESET_SOME_AUTHOR).when(mockedChangeSet).getAuthor(); + doReturn(CHANGESET_NORMALIZED_FILE_PATH).when(mockedChangeSet).getFilePath(); + doReturn(CHANGESET_STORED_FILE_PATH).when(mockedChangeSet).getStoredFilePath(); + doAnswer((invocation) -> invocation.getArgument(0)).when(mockedDatabase).escapeObjectName(anyString(),any()); + } + + @Test + public void generateSqlUsesStoredFilePathByDefault() { + List expectedWhereParams = Arrays.asList(CHANGESET_SOME_ID, CHANGESET_SOME_AUTHOR, CHANGESET_STORED_FILE_PATH); + verifyWhereParamsListInTheGeneratedStatement(expectedWhereParams); + } + + @Test + public void generateSqlUsesChangesetsNormalizedFilePathWhenChangeSetStoredFilePathIsNull() { + List expectedWhereParams = Arrays.asList(CHANGESET_SOME_ID, CHANGESET_SOME_AUTHOR, CHANGESET_NORMALIZED_FILE_PATH); + doReturn(null).when(mockedChangeSet).getStoredFilePath(); + verifyWhereParamsListInTheGeneratedStatement(expectedWhereParams); + } + + + private void verifyWhereParamsListInTheGeneratedStatement(List expectedWhereParams) { + + try (MockedStatic staticSqlGeneratorFactory = mockStatic(SqlGeneratorFactory.class)) { + staticSqlGeneratorFactory.when(SqlGeneratorFactory::getInstance).thenReturn(sqlGeneratorFactory); + UpdateChangeSetChecksumStatement statement = new UpdateChangeSetChecksumStatement(mockedChangeSet); + + sqlGenerator.generateSql(statement, mockedDatabase, null); + ArgumentCaptor statementCaptor = ArgumentCaptor.forClass(SqlStatement.class); + verify(sqlGeneratorFactory, times(1)) + .generateSql( + statementCaptor.capture(), eq(mockedDatabase)); + SqlStatement suppliedSqlStatement = statementCaptor.getValue(); + assertTrue("Expect the supplied Statement to be a UpdateStatmeent Instance", + UpdateStatement.class.isInstance(suppliedSqlStatement)); + UpdateStatement suppliedUpdateStmt = (UpdateStatement) suppliedSqlStatement; + List suppliedParams = suppliedUpdateStmt.getWhereParameters(); + assertEquals(expectedWhereParams, suppliedParams); + + } + } + +}