From 9c2b6c425f053799c5d50a74b4ac59434ae0e7f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Aug 2022 02:17:57 +0000 Subject: [PATCH 01/23] Bump snowflake-jdbc from 3.13.21 to 3.13.22 Bumps [snowflake-jdbc](https://github.com/snowflakedb/snowflake-jdbc) from 3.13.21 to 3.13.22. - [Release notes](https://github.com/snowflakedb/snowflake-jdbc/releases) - [Changelog](https://github.com/snowflakedb/snowflake-jdbc/blob/master/CHANGELOG.rst) - [Commits](https://github.com/snowflakedb/snowflake-jdbc/compare/3.13.21...3.13.22) --- updated-dependencies: - dependency-name: net.snowflake:snowflake-jdbc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- liquibase-dist/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/liquibase-dist/pom.xml b/liquibase-dist/pom.xml index 7f3f98581e9..3779ae00efa 100644 --- a/liquibase-dist/pom.xml +++ b/liquibase-dist/pom.xml @@ -77,7 +77,7 @@ net.snowflake snowflake-jdbc - 3.13.21 + 3.13.22 diff --git a/pom.xml b/pom.xml index b362852665e..7a3b2d7d774 100644 --- a/pom.xml +++ b/pom.xml @@ -227,7 +227,7 @@ net.snowflake snowflake-jdbc - 3.13.21 + 3.13.22 From b92087454106ba0c31a79d0caa67ec40d1dc18ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 02:32:50 +0000 Subject: [PATCH 02/23] Bump liquibase-sdk-maven-plugin from 0.9.1 to 0.10.18 Bumps [liquibase-sdk-maven-plugin](https://github.com/liquibase/liquibase-sdk-maven-plugin) from 0.9.1 to 0.10.18. - [Release notes](https://github.com/liquibase/liquibase-sdk-maven-plugin/releases) - [Commits](https://github.com/liquibase/liquibase-sdk-maven-plugin/compare/v0.9.1...v0.10.18) --- updated-dependencies: - dependency-name: org.liquibase.ext:liquibase-sdk-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- liquibase-dist/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liquibase-dist/pom.xml b/liquibase-dist/pom.xml index 0476786c831..110de0a2d0a 100644 --- a/liquibase-dist/pom.xml +++ b/liquibase-dist/pom.xml @@ -159,7 +159,7 @@ org.liquibase.ext liquibase-sdk-maven-plugin - 0.9.1 + 0.10.18 From ff9225b0ee2813d769f333e9fad804cc5e94f844 Mon Sep 17 00:00:00 2001 From: Adam Dobos Date: Thu, 1 Sep 2022 20:41:22 +0200 Subject: [PATCH 03/23] fix for case when current searchPath is an empty string (happens for us when running postgres with testcontainers for IT tests) --- .../liquibase/database/core/DatabaseUtils.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/liquibase-core/src/main/java/liquibase/database/core/DatabaseUtils.java b/liquibase-core/src/main/java/liquibase/database/core/DatabaseUtils.java index 0009d65f0f8..b6862196772 100644 --- a/liquibase-core/src/main/java/liquibase/database/core/DatabaseUtils.java +++ b/liquibase-core/src/main/java/liquibase/database/core/DatabaseUtils.java @@ -10,6 +10,7 @@ import liquibase.statement.core.RawSqlStatement; import liquibase.structure.core.Schema; import liquibase.util.StringUtil; +import org.apache.commons.lang3.StringUtils; public class DatabaseUtils { /** @@ -48,13 +49,15 @@ public static void initializeDatabase(String defaultCatalogName, String defaultS finalSearchPath = defaultSchemaName; } - //If existing search path entries are not quoted, quote them. Some databases do not show them as quoted even though they need to be (like $user or case sensitive schemas) - finalSearchPath += ", " + StringUtil.join(StringUtil.splitAndTrim(searchPath, ","), ",", (StringUtil.StringUtilFormatter) obj -> { - if (obj.startsWith("\"")) { - return obj; - } - return ((PostgresDatabase) database).quoteObject(obj, Schema.class); - }); + if (StringUtils.isNotBlank(searchPath)) { + //If existing search path entries are not quoted, quote them. Some databases do not show them as quoted even though they need to be (like $user or case sensitive schemas) + finalSearchPath += ", " + StringUtil.join(StringUtil.splitAndTrim(searchPath, ","), ",", (StringUtil.StringUtilFormatter) obj -> { + if (obj.startsWith("\"")) { + return obj; + } + return ((PostgresDatabase) database).quoteObject(obj, Schema.class); + }); + } executor.execute(new RawSqlStatement("SET SEARCH_PATH TO " + finalSearchPath)); } From d65c825a85ed5301b8cb6472b99d2707f12fa1f4 Mon Sep 17 00:00:00 2001 From: Daniel Mallorga Date: Thu, 1 Sep 2022 15:44:29 -0300 Subject: [PATCH 04/23] Added smallserial alias and finishInitialization method to SmallInt type. Also updated CreateTableChangeTest to validate the expected sql is generated for smallserial type. --- .../liquibase/datatype/core/SmallIntType.java | 19 +++++++++++++++++-- .../change/core/CreateTableChangeTest.groovy | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/liquibase-core/src/main/java/liquibase/datatype/core/SmallIntType.java b/liquibase-core/src/main/java/liquibase/datatype/core/SmallIntType.java index f50bf6473c4..945b1ba3365 100644 --- a/liquibase-core/src/main/java/liquibase/datatype/core/SmallIntType.java +++ b/liquibase-core/src/main/java/liquibase/datatype/core/SmallIntType.java @@ -1,17 +1,17 @@ package liquibase.datatype.core; +import liquibase.GlobalConfiguration; import liquibase.change.core.LoadDataChange; import liquibase.database.Database; import liquibase.database.core.*; import liquibase.datatype.DataTypeInfo; import liquibase.datatype.DatabaseDataType; import liquibase.datatype.LiquibaseDataType; -import liquibase.exception.DatabaseException; import liquibase.statement.DatabaseFunction; import java.util.Locale; -@DataTypeInfo(name="smallint", aliases = {"java.sql.Types.SMALLINT", "int2"}, minParameters = 0, maxParameters = 1, priority = LiquibaseDataType.PRIORITY_DEFAULT) +@DataTypeInfo(name="smallint", aliases = {"java.sql.Types.SMALLINT", "int2", "smallserial"}, minParameters = 0, maxParameters = 1, priority = LiquibaseDataType.PRIORITY_DEFAULT) public class SmallIntType extends LiquibaseDataType { private boolean autoIncrement; @@ -52,6 +52,12 @@ public DatabaseDataType toDatabaseDataType(Database database) { if (((PostgresDatabase) database).useSerialDatatypes()) { return new DatabaseDataType("SMALLSERIAL"); } + } else { + if (GlobalConfiguration.CONVERT_DATA_TYPES.getCurrentValue() || this.getRawDefinition() == null) { + return new DatabaseDataType("SMALLINT"); + } else { + return new DatabaseDataType(this.getRawDefinition()); + } } return new DatabaseDataType("SMALLINT"); //always smallint regardless of parameters passed } @@ -79,4 +85,13 @@ public String objectToSql(Object value, Database database) { public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() { return LoadDataChange.LOAD_DATA_TYPE.NUMERIC; } + + @Override + public void finishInitialization(String originalDefinition) { + super.finishInitialization(originalDefinition); + + if (originalDefinition.toLowerCase(Locale.US).contains("serial")) { + autoIncrement = true; + } + } } diff --git a/liquibase-core/src/test/groovy/liquibase/change/core/CreateTableChangeTest.groovy b/liquibase-core/src/test/groovy/liquibase/change/core/CreateTableChangeTest.groovy index 7e7ed721661..4d6d0212bad 100644 --- a/liquibase-core/src/test/groovy/liquibase/change/core/CreateTableChangeTest.groovy +++ b/liquibase-core/src/test/groovy/liquibase/change/core/CreateTableChangeTest.groovy @@ -293,7 +293,7 @@ public class CreateTableChangeTest extends StandardChangeTest { type | autoinc | database | expected "int" | true | new MockDatabase() | "CREATE TABLE test_table (id INT null)" "SERIAL" | false | new PostgresDatabase() | "CREATE TABLE test_table (id INTEGER GENERATED BY DEFAULT AS IDENTITY)" - "SMALLSERIAL" | false | new PostgresDatabase() | "CREATE TABLE test_table (id SMALLSERIAL)" + "SMALLSERIAL" | false | new PostgresDatabase() | "CREATE TABLE test_table (id SMALLINT GENERATED BY DEFAULT AS IDENTITY)" "BIGSERIAL" | false | new PostgresDatabase() | "CREATE TABLE test_table (id BIGINT GENERATED BY DEFAULT AS IDENTITY)" } From a684dad9277d134c8cfe4530d8b24b155f16ed6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Sep 2022 12:59:40 +0000 Subject: [PATCH 05/23] Bump opencsv from 5.6 to 5.7.0 Bumps opencsv from 5.6 to 5.7.0. --- updated-dependencies: - dependency-name: com.opencsv:opencsv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- liquibase-core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liquibase-core/pom.xml b/liquibase-core/pom.xml index e0ecd22be79..02e995a1ce3 100644 --- a/liquibase-core/pom.xml +++ b/liquibase-core/pom.xml @@ -111,7 +111,7 @@ com.opencsv opencsv - 5.6 + 5.7.0 From 4f373ff200129e151ad8e59f8499d803a16a9a3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Sep 2022 17:10:41 +0000 Subject: [PATCH 06/23] Bump robinraju/release-downloader from 1.4 to 1.5 Bumps [robinraju/release-downloader](https://github.com/robinraju/release-downloader) from 1.4 to 1.5. - [Release notes](https://github.com/robinraju/release-downloader/releases) - [Commits](https://github.com/robinraju/release-downloader/compare/v1.4...v1.5) --- updated-dependencies: - dependency-name: robinraju/release-downloader dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/release-published.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-published.yml b/.github/workflows/release-published.yml index d54d5e73403..a0a8cf0d50f 100644 --- a/.github/workflows/release-published.yml +++ b/.github/workflows/release-published.yml @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download release assets - uses: robinraju/release-downloader@v1.4 + uses: robinraju/release-downloader@v1.5 with: repository: "liquibase/liquibase" tag: "${{ needs.setup.outputs.tag }}" @@ -175,7 +175,7 @@ jobs: contents: read steps: - name: Download release javadocs - uses: robinraju/release-downloader@v1.4 + uses: robinraju/release-downloader@v1.5 with: repository: "liquibase/liquibase" tag: "${{ needs.setup.outputs.tag }}" From 55dd47b237ddbfba926d25f228824d2146e7ad7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Sep 2022 02:18:30 +0000 Subject: [PATCH 07/23] Bump mockito-inline from 4.7.0 to 4.8.0 Bumps [mockito-inline](https://github.com/mockito/mockito) from 4.7.0 to 4.8.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v4.8.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-inline dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9eb9ae330b9..58f76b58fd6 100644 --- a/pom.xml +++ b/pom.xml @@ -124,7 +124,7 @@ org.mockito mockito-inline - 4.7.0 + 4.8.0 test From 125e820db22e9e22d48bb17dcbfce4b819c8b9d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Sep 2022 02:18:37 +0000 Subject: [PATCH 08/23] Bump mockito-core from 4.7.0 to 4.8.0 Bumps [mockito-core](https://github.com/mockito/mockito) from 4.7.0 to 4.8.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v4.8.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9eb9ae330b9..ba930464afd 100644 --- a/pom.xml +++ b/pom.xml @@ -118,7 +118,7 @@ org.mockito mockito-core - 4.7.0 + 4.8.0 test From a1b4895cf459210661be74c9c8f7939969ed6103 Mon Sep 17 00:00:00 2001 From: Nathan Voxland Date: Fri, 9 Sep 2022 16:10:36 -0500 Subject: [PATCH 09/23] Upgraded installer to use JDK 17.0.4.1 --- liquibase-dist/src/main/install4j/liquibase.install4j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liquibase-dist/src/main/install4j/liquibase.install4j b/liquibase-dist/src/main/install4j/liquibase.install4j index d8eafcc19fb..b7751d6c10e 100644 --- a/liquibase-dist/src/main/install4j/liquibase.install4j +++ b/liquibase-dist/src/main/install4j/liquibase.install4j @@ -16,7 +16,7 @@ sqlite-* - + From 31484f1722cd3754d701e3f28c1d1e59b5ed8377 Mon Sep 17 00:00:00 2001 From: Nathan Voxland Date: Wed, 14 Sep 2022 23:09:16 -0500 Subject: [PATCH 10/23] Trying to fix build --- .github/workflows/snyk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index fada4b77991..4800d839d46 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -39,7 +39,7 @@ jobs: ## This builds and installs the sub-modules so they are available. The liquibase-core:test module has to be installed manually since it wasn't coming along with the regular mvn install - name: Install modules run: | - mvn -B -pl '!liquibase-dist' test-compile install -DskipTests=true + mvn -B test-compile install -DskipTests=true mvn -B org.apache.maven.plugins:maven-install-plugin:3.0.0-M1:install-file -Dfile=liquibase-core/target/liquibase-core-0-SNAPSHOT-tests.jar -Dpackaging=jar -Dclassifier=tests -DgroupId=org.liquibase -DartifactId=liquibase-core ## snyk monitor requires --all-projects because otherwise it only reports on the dependencies of one of the sub-modules. It would be nice if we could have one snyk project which included all the sub-modules in it, but that doesn't seem possible at this point From d87b4353d9a29e1b981c78b54f7846c170769086 Mon Sep 17 00:00:00 2001 From: Bezout Date: Fri, 16 Sep 2022 22:25:22 +0200 Subject: [PATCH 11/23] AlterSequence: include NOORDER clause ordered="false" is specified (#1044) * fix alterSequence ordered documentation * fix alterSequence NOORDER * ORDER add space (like others) Co-authored-by: Nathan Voxland --- .../java/liquibase/change/core/AlterSequenceChange.java | 2 +- .../sqlgenerator/core/AlterSequenceGenerator.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/liquibase-core/src/main/java/liquibase/change/core/AlterSequenceChange.java b/liquibase-core/src/main/java/liquibase/change/core/AlterSequenceChange.java index 36ec3f6fcb8..185fc9075c6 100644 --- a/liquibase-core/src/main/java/liquibase/change/core/AlterSequenceChange.java +++ b/liquibase-core/src/main/java/liquibase/change/core/AlterSequenceChange.java @@ -81,7 +81,7 @@ public void setMinValue(BigInteger minValue) { this.minValue = minValue; } - @DatabaseChangeProperty(description = "Does the sequence need to be guaranteed to be genererated inm the order of request?") + @DatabaseChangeProperty(description = "Does the sequence need to be guaranteed to be generated in the order of request?") public Boolean isOrdered() { return ordered; } diff --git a/liquibase-core/src/main/java/liquibase/sqlgenerator/core/AlterSequenceGenerator.java b/liquibase-core/src/main/java/liquibase/sqlgenerator/core/AlterSequenceGenerator.java index 694a2477217..8eaf763dea8 100644 --- a/liquibase-core/src/main/java/liquibase/sqlgenerator/core/AlterSequenceGenerator.java +++ b/liquibase-core/src/main/java/liquibase/sqlgenerator/core/AlterSequenceGenerator.java @@ -68,7 +68,9 @@ public Sql[] generateSql(AlterSequenceStatement statement, Database database, Sq if (statement.getOrdered() != null) { if (statement.getOrdered()) { - buffer.append(" ORDER"); + buffer.append(" ORDER "); + } else { + buffer.append(" NOORDER "); } } @@ -94,8 +96,8 @@ public Sql[] generateSql(AlterSequenceStatement statement, Database database, Sq } } - return new Sql[]{ - new UnparsedSql(buffer.toString(), getAffectedSequence(statement)) + return new Sql[] { + new UnparsedSql(buffer.toString(), getAffectedSequence(statement)) }; } From 8512769590ae3175982c34b4f91d3ff33abdf377 Mon Sep 17 00:00:00 2001 From: Florent Biville <445792+fbiville@users.noreply.github.com> Date: Mon, 19 Sep 2022 23:38:59 +0200 Subject: [PATCH 12/23] Upper case result columns only for case-insensitive databases (#3102) Fixes #3071 Co-authored-by: Nathan Voxland --- .../java/liquibase/executor/jvm/ColumnMapRowMapper.java | 9 +++++++++ .../main/java/liquibase/executor/jvm/JdbcExecutor.java | 2 +- .../java/liquibase/snapshot/JdbcDatabaseSnapshot.java | 2 +- .../src/main/java/liquibase/snapshot/ResultSetCache.java | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/liquibase-core/src/main/java/liquibase/executor/jvm/ColumnMapRowMapper.java b/liquibase-core/src/main/java/liquibase/executor/jvm/ColumnMapRowMapper.java index 65e5565f6f2..79fa6d1d722 100644 --- a/liquibase-core/src/main/java/liquibase/executor/jvm/ColumnMapRowMapper.java +++ b/liquibase-core/src/main/java/liquibase/executor/jvm/ColumnMapRowMapper.java @@ -24,6 +24,12 @@ @SuppressWarnings({"unchecked"}) public class ColumnMapRowMapper implements RowMapper { + private final boolean caseSensitiveDatabase; + + public ColumnMapRowMapper(boolean caseSensitiveDatabase) { + this.caseSensitiveDatabase = caseSensitiveDatabase; + } + @Override public Object mapRow(ResultSet rs, int rowNum) throws SQLException { ResultSetMetaData rsmd = rs.getMetaData(); @@ -55,6 +61,9 @@ protected Map createColumnMap(int columnCount) { * @see java.sql.ResultSetMetaData#getColumnName */ protected String getColumnKey(String columnName) { + if (this.caseSensitiveDatabase) { + return columnName; + } return columnName.toUpperCase(Locale.US); } diff --git a/liquibase-core/src/main/java/liquibase/executor/jvm/JdbcExecutor.java b/liquibase-core/src/main/java/liquibase/executor/jvm/JdbcExecutor.java index 3a0e69ffebd..e054711a506 100644 --- a/liquibase-core/src/main/java/liquibase/executor/jvm/JdbcExecutor.java +++ b/liquibase-core/src/main/java/liquibase/executor/jvm/JdbcExecutor.java @@ -319,7 +319,7 @@ public SqlStatement getStatement() { * @see ColumnMapRowMapper */ protected RowMapper getColumnMapRowMapper() { - return new ColumnMapRowMapper(); + return new ColumnMapRowMapper(database.isCaseSensitive()); } /** diff --git a/liquibase-core/src/main/java/liquibase/snapshot/JdbcDatabaseSnapshot.java b/liquibase-core/src/main/java/liquibase/snapshot/JdbcDatabaseSnapshot.java index 848233b5e19..ab52633a391 100644 --- a/liquibase-core/src/main/java/liquibase/snapshot/JdbcDatabaseSnapshot.java +++ b/liquibase-core/src/main/java/liquibase/snapshot/JdbcDatabaseSnapshot.java @@ -1035,7 +1035,7 @@ protected List extract(ResultSet resultSet, boolean informixIndexTrim List result; try { - result = (List) new RowMapperNotNullConstraintsResultSetExtractor(new ColumnMapRowMapper() { + result = (List) new RowMapperNotNullConstraintsResultSetExtractor(new ColumnMapRowMapper(database.isCaseSensitive()) { @Override protected Object getColumnValue(ResultSet rs, int index) throws SQLException { Object value = super.getColumnValue(rs, index); diff --git a/liquibase-core/src/main/java/liquibase/snapshot/ResultSetCache.java b/liquibase-core/src/main/java/liquibase/snapshot/ResultSetCache.java index 93f5ec18241..f187c13579e 100644 --- a/liquibase-core/src/main/java/liquibase/snapshot/ResultSetCache.java +++ b/liquibase-core/src/main/java/liquibase/snapshot/ResultSetCache.java @@ -314,7 +314,7 @@ protected List extract(ResultSet resultSet, final boolean informixInd List result; List returnList = new ArrayList<>(); try { - result = (List) new RowMapperResultSetExtractor(new ColumnMapRowMapper() { + result = (List) new RowMapperResultSetExtractor(new ColumnMapRowMapper(database.isCaseSensitive()) { @Override protected Object getColumnValue(ResultSet rs, int index) throws SQLException { Object value = super.getColumnValue(rs, index); From 73332ad47e6c87aa939c7e27f597827ae37e2e96 Mon Sep 17 00:00:00 2001 From: Nathan Voxland Date: Mon, 19 Sep 2022 14:39:27 -0700 Subject: [PATCH 13/23] Added ThreadLocalScopeManager (#3240) * Added ThreadLocalScopeManager * Made SqlGeneratorFactory getGenerators() synchronized as it sometimes has concurrency issues --- .../src/main/java/liquibase/Scope.java | 6 +- .../liquibase/ThreadLocalScopeManager.java | 42 +++ .../sqlgenerator/SqlGeneratorFactory.java | 2 +- .../ThreadLocalScopeManagerTest.groovy | 254 ++++++++++++++++++ .../ClassLoaderResourceAccessorTest.groovy | 3 + .../FileSystemResourceAccessorTest.groovy | 10 +- .../test/resources/com/example/changelog.xml | 29 ++ 7 files changed, 337 insertions(+), 9 deletions(-) create mode 100644 liquibase-core/src/main/java/liquibase/ThreadLocalScopeManager.java create mode 100644 liquibase-core/src/test/groovy/liquibase/ThreadLocalScopeManagerTest.groovy create mode 100644 liquibase-core/src/test/resources/com/example/changelog.xml diff --git a/liquibase-core/src/main/java/liquibase/Scope.java b/liquibase-core/src/main/java/liquibase/Scope.java index 0910d57134c..c2598afa09a 100644 --- a/liquibase-core/src/main/java/liquibase/Scope.java +++ b/liquibase-core/src/main/java/liquibase/Scope.java @@ -262,7 +262,7 @@ public boolean has(Enum key) { } - public T get(Enum key, Class type) { + public synchronized T get(Enum key, Class type) { return get(key.name(), type); } @@ -288,7 +288,7 @@ public T get(String key, Class type) { * If the value is not defined, the passed defaultValue is returned. * The value is converted to the given type if necessary using {@link liquibase.util.ObjectUtil#convert(Object, Class)}. */ - public T get(String key, T defaultValue) { + public synchronized T get(String key, T defaultValue) { Class type; if (defaultValue == null) { type = Object.class; @@ -307,7 +307,7 @@ public T get(String key, T defaultValue) { * Looks up the singleton object of the given type. If the singleton has not been created yet, it will be instantiated. * The singleton is a singleton based on the root scope and the same object will be returned for all child scopes of the root. */ - public T getSingleton(Class type) { + public synchronized T getSingleton(Class type) { if (getParent() != null) { return getParent().getSingleton(type); } diff --git a/liquibase-core/src/main/java/liquibase/ThreadLocalScopeManager.java b/liquibase-core/src/main/java/liquibase/ThreadLocalScopeManager.java new file mode 100644 index 00000000000..fbaeff75c3f --- /dev/null +++ b/liquibase-core/src/main/java/liquibase/ThreadLocalScopeManager.java @@ -0,0 +1,42 @@ +package liquibase; + +/** + * An alternative to {@link SingletonScopeManager} which manages a separate Scope per thread.

+ * Integrations that would prefer to use this scope manager can call
Scope.setScopeManager(new ThreadLocalScopeManager())
. + *

+ * The value of Scope.getCurrentScope() at the time of the ThreadLocalScopeManger's creation will be the basis of all scopes created after setScopeManager() is changed, + * so you will generally want to setScopeManager as soon as possible. + */ +@SuppressWarnings("java:S5164") +public class ThreadLocalScopeManager extends ScopeManager { + + private final Scope rootScope; + private final ThreadLocal threadLocalScopes = new ThreadLocal<>(); + + public ThreadLocalScopeManager() { + this.rootScope = Scope.getCurrentScope(); + } + + @Override + public synchronized Scope getCurrentScope() { + Scope current = threadLocalScopes.get(); + + if (current == null) { + threadLocalScopes.set(rootScope); + current = rootScope; + } + + return current; + } + + @Override + protected synchronized void setCurrentScope(Scope scope) { + this.threadLocalScopes.set(scope); + } + @Override + protected Scope init(Scope scope) throws Exception { + return rootScope; + } + + +} \ No newline at end of file diff --git a/liquibase-core/src/main/java/liquibase/sqlgenerator/SqlGeneratorFactory.java b/liquibase-core/src/main/java/liquibase/sqlgenerator/SqlGeneratorFactory.java index 8469509cd73..2681329386e 100644 --- a/liquibase-core/src/main/java/liquibase/sqlgenerator/SqlGeneratorFactory.java +++ b/liquibase-core/src/main/java/liquibase/sqlgenerator/SqlGeneratorFactory.java @@ -82,7 +82,7 @@ protected Collection getGenerators() { return generators; } - public SortedSet getGenerators(SqlStatement statement, Database database) { + public synchronized SortedSet getGenerators(SqlStatement statement, Database database) { String databaseName = null; if (database == null) { databaseName = "NULL"; diff --git a/liquibase-core/src/test/groovy/liquibase/ThreadLocalScopeManagerTest.groovy b/liquibase-core/src/test/groovy/liquibase/ThreadLocalScopeManagerTest.groovy new file mode 100644 index 00000000000..1a67d8f7e7b --- /dev/null +++ b/liquibase-core/src/test/groovy/liquibase/ThreadLocalScopeManagerTest.groovy @@ -0,0 +1,254 @@ +package liquibase + +import liquibase.changelog.ChangeSet +import liquibase.database.jvm.JdbcConnection +import liquibase.exception.LiquibaseException +import liquibase.resource.ClassLoaderResourceAccessor +import spock.lang.Specification + +import java.sql.* +import java.util.concurrent.* +import java.util.stream.Collectors + +import static org.junit.Assert.* + +class ThreadLocalScopeManagerTest extends Specification { + + private static final String DATABASE_NAME_PREFIX = "DB_MT_" + private final ExecutorService executor = Executors.newCachedThreadPool() + private final Map liveConnections = new ConcurrentHashMap<>() + + private ScopeManager originalScopeManager + + def setup() { + Scope.getCurrentScope() + originalScopeManager = Scope.scopeManager + Scope.setScopeManager(new ThreadLocalScopeManager()) + } + + def cleanup() { + teardownLiveConnections() + shutdownExecutorService() + + Scope.setScopeManager(originalScopeManager); + + } + + void "maintain databases in parallel"() { + when: + /* + * 4 threads seems to be sufficient to provoke most errors + */ + final int threadCount = Math.min(16, Runtime.getRuntime().availableProcessors() * 2) + + System.out.println("Liquibase threading test will use " + threadCount + " threads.") + + final List> maintainTasks = new ArrayList<>() + + /* + * We want to stress initialization as much as possible, so we + * wait for all thread ready before we start. + */ + final ThreadAligner threadAligner = new ThreadAligner(threadCount) + + for (int i = 0; i < threadCount; i++) { + final String dbName = DATABASE_NAME_PREFIX + i + + liveConnections.put(dbName, MemoryDatabase.create(dbName)) + + maintainTasks.add(executor.submit({ -> + threadAligner.awaitAllReady() + maintainDatabase(dbName) + })) + } + + then: + for (def task : maintainTasks) { + task.get(30, TimeUnit.SECONDS) + } + } + + + private void maintainDatabase(final String dbName) { + final MemoryDatabase db = getDatabase(dbName) + + Connection con = db.getConnection() + try { + final Liquibase liquibase = new Liquibase( + "com/example/changelog.xml", + new ClassLoaderResourceAccessor(), + new JdbcConnection(con)) + + final List pending = liquibase.listUnrunChangeSets(new Contexts(), new LabelExpression()) + assert !pending.isEmpty(): "Expected pending database changesets" + + liquibase.update(new Contexts(), new LabelExpression()) + + final List tableNames = db.queryTables() + + assert tableNames.contains("table1") + assert tableNames.contains("table2") + + } catch (SQLException | LiquibaseException e) { + throw new IllegalStateException(e.getMessage(), e) + } + } + + private MemoryDatabase getDatabase(final String dbName) { + final MemoryDatabase db = liveConnections.get(dbName) + assertNotNull("Memory database not created for " + dbName, db) + return db + } + + private void shutdownExecutorService() { + executor.shutdownNow() + + try { + executor.awaitTermination(5, TimeUnit.SECONDS) + } catch (InterruptedException e) { + Thread.currentThread().interrupt() + throw new IllegalStateException("Failed to terminate all threads in a timely fashion") + } + } + + private void teardownLiveConnections() { + for (MemoryDatabase db : liveConnections.values()) { + db.close() + } + } + + private static class ThreadAligner { + + private final CyclicBarrier barrier + + ThreadAligner(int threads) { + this.barrier = new CyclicBarrier(threads) + } + + void awaitAllReady() { + try { + barrier.await() + } catch (InterruptedException e) { + Thread.currentThread().interrupt() + throw new IllegalStateException(e) + } catch (BrokenBarrierException e) { + throw new IllegalStateException(e) + } + } + } + + static class MemoryDatabase implements AutoCloseable { + + private static final String SENSING_TABLE = "_fake_lock" + + private final String connectionUrl + private final Connection mainConnection + + private boolean closed = false + + private MemoryDatabase(String connectionUrl, Connection mainConnection) { + this.connectionUrl = connectionUrl + this.mainConnection = mainConnection + } + + static synchronized MemoryDatabase create(String dbName) { + MemoryDatabase db = createUnvalidated(dbName) + + if (db.hasSensingTable()) { + db.close() + throw new IllegalStateException("Database already exists: " + dbName) + } + + db.createSensingTable() + + return db + } + + @Override + synchronized void close() { + closed = true + try { + mainConnection.close() + } catch (SQLException e) { + throw new IllegalStateException(e) + } + } + + synchronized Connection getConnection() { + if (closed) { + throw new IllegalStateException("Fake connection factory is closed!") + } + + try { + return createConnection(this.connectionUrl) + } catch (SQLException e) { + throw new IllegalStateException(e) + } + } + + List queryTables() { + return query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA <> 'INFORMATION_SCHEMA'").stream() + .map({ rec -> rec.get("table_name").toLowerCase() }) + .collect(Collectors.toList()) + } + + List> query(final String sql) { + List> l = new ArrayList<>() + + Statement stat = this.mainConnection.createStatement() + ResultSet rc = stat.executeQuery(sql) + try { + final ResultSetMetaData m = rc.getMetaData() + while (rc.next()) { + Map record = new LinkedHashMap<>() + for (int i = 1; i <= m.getColumnCount(); i++) { + record.put(m.getColumnName(i).toLowerCase(), rc.getString(i)) + } + l.add(record) + } + } catch (SQLException e) { + throw new IllegalStateException(e) + } + + return l + } + + int update(String sql) { + Statement stat = this.mainConnection.createStatement() + try { + return stat.executeUpdate(sql) + } catch (SQLException e) { + throw new IllegalStateException(e) + } + } + + + private static MemoryDatabase createUnvalidated(String dbName) { + try { + final String connectionUrl = "jdbc:h2:mem:" + dbName + final Connection con = createConnection(connectionUrl) + return new MemoryDatabase(connectionUrl, con) + } catch (SQLException e) { + throw new IllegalStateException(e) + } + } + + private boolean hasSensingTable() { + return queryTables().stream().anyMatch(SENSING_TABLE.&equals) + } + + private void createSensingTable() { + final String sql = "CREATE TABLE " + SENSING_TABLE + " ( DUMMY VARCHAR(100), PRIMARY KEY (DUMMY))" + PreparedStatement stat = this.mainConnection.prepareStatement(sql) + try { + stat.executeUpdate() + } catch (SQLException e) { + throw new IllegalStateException(e) + } + } + + private static Connection createConnection(final String connectionUrl) throws SQLException { + return DriverManager.getConnection(connectionUrl, "sa", "") + } + } +} diff --git a/liquibase-core/src/test/groovy/liquibase/resource/ClassLoaderResourceAccessorTest.groovy b/liquibase-core/src/test/groovy/liquibase/resource/ClassLoaderResourceAccessorTest.groovy index c0a258e8b0f..de71498a2a8 100644 --- a/liquibase-core/src/test/groovy/liquibase/resource/ClassLoaderResourceAccessorTest.groovy +++ b/liquibase-core/src/test/groovy/liquibase/resource/ClassLoaderResourceAccessorTest.groovy @@ -87,6 +87,7 @@ class ClassLoaderResourceAccessorTest extends Specification { [ null, "com/example", true, true, true, [ + "com/example/changelog.xml", "com/example/directory", "com/example/directory/file-in-directory.txt", "com/example/everywhere", @@ -119,6 +120,7 @@ class ClassLoaderResourceAccessorTest extends Specification { [ null, "com/example", false, true, true, [ + "com/example/changelog.xml", "com/example/directory", "com/example/everywhere", "com/example/file with space.txt", @@ -135,6 +137,7 @@ class ClassLoaderResourceAccessorTest extends Specification { [ null, "com/example", false, true, false, [ + "com/example/changelog.xml", "com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", diff --git a/liquibase-core/src/test/groovy/liquibase/resource/FileSystemResourceAccessorTest.groovy b/liquibase-core/src/test/groovy/liquibase/resource/FileSystemResourceAccessorTest.groovy index 25058de0d95..6b577c69d5b 100644 --- a/liquibase-core/src/test/groovy/liquibase/resource/FileSystemResourceAccessorTest.groovy +++ b/liquibase-core/src/test/groovy/liquibase/resource/FileSystemResourceAccessorTest.groovy @@ -167,12 +167,12 @@ class FileSystemResourceAccessorTest extends Specification { where: relativeTo | path | recursive | includeFiles | includeDirectories | expected - null | "com/example" | false | true | false | ["com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", "com/example/my-logic.sql", "com/example/users.csv"] - "com" | "example" | false | true | false | ["com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", "com/example/my-logic.sql", "com/example/users.csv"] + null | "com/example" | false | true | false | ["com/example/changelog.xml", "com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", "com/example/my-logic.sql", "com/example/users.csv"] + "com" | "example" | false | true | false | ["com/example/changelog.xml", "com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", "com/example/my-logic.sql", "com/example/users.csv"] "com/example/users.csv" | "everywhere" | false | true | false | ["com/example/everywhere/file-everywhere.txt", "com/example/everywhere/other-file-everywhere.txt"] - null | "com/example" | false | true | true | ["com/example/directory", "com/example/everywhere", "com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", "com/example/jar", "com/example/liquibase", "com/example/my-logic.sql", "com/example/shared", "com/example/users.csv", "com/example/zip"] - null | "com/example" | true | true | true | ["com/example/directory", "com/example/directory/file-in-directory.txt", "com/example/everywhere", "com/example/everywhere/file-everywhere.txt", "com/example/everywhere/other-file-everywhere.txt", "com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", "com/example/jar", "com/example/jar/file-in-jar.txt", "com/example/liquibase", "com/example/liquibase/change", "com/example/liquibase/change/ColumnConfig.class", "com/example/liquibase/change/ComputedConfig.class", "com/example/liquibase/change/CreateTableExampleChange.class", "com/example/liquibase/change/DefaultConstraintConfig.class", "com/example/liquibase/change/IdentityConfig.class", "com/example/liquibase/change/KeyColumnConfig.class", "com/example/liquibase/change/PrimaryKeyConfig.class", "com/example/liquibase/change/UniqueConstraintConfig.class", "com/example/my-logic.sql", "com/example/shared", "com/example/shared/file-in-jar.txt", "com/example/shared/file-in-zip.txt", "com/example/users.csv", "com/example/zip", "com/example/zip/file-in-zip.txt"] - null | "com/example" | true | true | false | ["com/example/directory/file-in-directory.txt", "com/example/everywhere/file-everywhere.txt", "com/example/everywhere/other-file-everywhere.txt", "com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", "com/example/jar/file-in-jar.txt", "com/example/liquibase/change/ColumnConfig.class", "com/example/liquibase/change/ComputedConfig.class", "com/example/liquibase/change/CreateTableExampleChange.class", "com/example/liquibase/change/DefaultConstraintConfig.class", "com/example/liquibase/change/IdentityConfig.class", "com/example/liquibase/change/KeyColumnConfig.class", "com/example/liquibase/change/PrimaryKeyConfig.class", "com/example/liquibase/change/UniqueConstraintConfig.class", "com/example/my-logic.sql", "com/example/shared/file-in-jar.txt", "com/example/shared/file-in-zip.txt", "com/example/users.csv", "com/example/zip/file-in-zip.txt"] + null | "com/example" | false | true | true | ["com/example/changelog.xml", "com/example/directory", "com/example/everywhere", "com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", "com/example/jar", "com/example/liquibase", "com/example/my-logic.sql", "com/example/shared", "com/example/users.csv", "com/example/zip"] + null | "com/example" | true | true | true | ["com/example/changelog.xml", "com/example/directory", "com/example/directory/file-in-directory.txt", "com/example/everywhere", "com/example/everywhere/file-everywhere.txt", "com/example/everywhere/other-file-everywhere.txt", "com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", "com/example/jar", "com/example/jar/file-in-jar.txt", "com/example/liquibase", "com/example/liquibase/change", "com/example/liquibase/change/ColumnConfig.class", "com/example/liquibase/change/ComputedConfig.class", "com/example/liquibase/change/CreateTableExampleChange.class", "com/example/liquibase/change/DefaultConstraintConfig.class", "com/example/liquibase/change/IdentityConfig.class", "com/example/liquibase/change/KeyColumnConfig.class", "com/example/liquibase/change/PrimaryKeyConfig.class", "com/example/liquibase/change/UniqueConstraintConfig.class", "com/example/my-logic.sql", "com/example/shared", "com/example/shared/file-in-jar.txt", "com/example/shared/file-in-zip.txt", "com/example/users.csv", "com/example/zip", "com/example/zip/file-in-zip.txt"] + null | "com/example" | true | true | false | ["com/example/changelog.xml", "com/example/directory/file-in-directory.txt", "com/example/everywhere/file-everywhere.txt", "com/example/everywhere/other-file-everywhere.txt", "com/example/file with space.txt", "com/example/file-in-jar.txt", "com/example/file-in-zip.txt", "com/example/jar/file-in-jar.txt", "com/example/liquibase/change/ColumnConfig.class", "com/example/liquibase/change/ComputedConfig.class", "com/example/liquibase/change/CreateTableExampleChange.class", "com/example/liquibase/change/DefaultConstraintConfig.class", "com/example/liquibase/change/IdentityConfig.class", "com/example/liquibase/change/KeyColumnConfig.class", "com/example/liquibase/change/PrimaryKeyConfig.class", "com/example/liquibase/change/UniqueConstraintConfig.class", "com/example/my-logic.sql", "com/example/shared/file-in-jar.txt", "com/example/shared/file-in-zip.txt", "com/example/users.csv", "com/example/zip/file-in-zip.txt"] null | "com/example" | true | false | true | ["com/example/directory", "com/example/everywhere", "com/example/jar", "com/example/liquibase", "com/example/liquibase/change", "com/example/shared", "com/example/zip"] null | "com/example" | true | false | false | [] null | "com/example" | false | false | false | [] diff --git a/liquibase-core/src/test/resources/com/example/changelog.xml b/liquibase-core/src/test/resources/com/example/changelog.xml new file mode 100644 index 00000000000..4fb9b9f3d35 --- /dev/null +++ b/liquibase-core/src/test/resources/com/example/changelog.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 81600bf8fac1e8b29e23948435f513feff5abf98 Mon Sep 17 00:00:00 2001 From: Trent Date: Mon, 19 Sep 2022 15:39:52 -0600 Subject: [PATCH 14/23] Add year and copyright owner to license (#3227) --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index d6456956733..e0cfd5a90e0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2022 Liquibase Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 5e3f36dc4832ec84314facf986357fe77f64f880 Mon Sep 17 00:00:00 2001 From: Jan Durovec Date: Mon, 19 Sep 2022 23:40:43 +0200 Subject: [PATCH 15/23] Fix missing catalog on add column rollback (#921) When rollback script for "add column" change is generated, catalog name is missing from generated SQL. As a result of that the rollback may not work if the affected table is not in the default catalog. --- .../liquibase/change/core/AddColumnChange.java | 1 + .../liquibase/change/AddColumnChangeTest.java | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/liquibase-core/src/main/java/liquibase/change/core/AddColumnChange.java b/liquibase-core/src/main/java/liquibase/change/core/AddColumnChange.java index f49bc9d4f81..99b61c3d07a 100644 --- a/liquibase-core/src/main/java/liquibase/change/core/AddColumnChange.java +++ b/liquibase-core/src/main/java/liquibase/change/core/AddColumnChange.java @@ -214,6 +214,7 @@ protected Change[] createInverses() { List inverses = new ArrayList<>(); DropColumnChange inverse = new DropColumnChange(); + inverse.setCatalogName(getCatalogName()); inverse.setSchemaName(getSchemaName()); inverse.setTableName(getTableName()); diff --git a/liquibase-core/src/test/java/liquibase/change/AddColumnChangeTest.java b/liquibase-core/src/test/java/liquibase/change/AddColumnChangeTest.java index 9532167bf3a..1100d476c5d 100644 --- a/liquibase-core/src/test/java/liquibase/change/AddColumnChangeTest.java +++ b/liquibase-core/src/test/java/liquibase/change/AddColumnChangeTest.java @@ -3,8 +3,10 @@ import liquibase.change.core.AddColumnChange; import liquibase.database.core.DB2Database; import liquibase.database.core.MockDatabase; +import liquibase.exception.RollbackImpossibleException; import liquibase.statement.SqlStatement; import liquibase.statement.core.AddColumnStatement; +import liquibase.statement.core.DropColumnStatement; import liquibase.statement.core.ReorganizeTableStatement; import org.junit.Assert; import org.junit.Test; @@ -93,4 +95,20 @@ public void generateStatements_singleColumn_null_uniqueConstraintName() { AddColumnStatement stmt = (AddColumnStatement)statements[0]; Assert.assertNull(stmt.getUniqueStatementName()); } + + @Test + public void generateRollbackStatements_catalog_schema_table() throws RollbackImpossibleException { + AddColumnChange change = new AddColumnChange(); + change.setCatalogName("catalog1"); + change.setSchemaName("schema1"); + change.setTableName("table1"); + + SqlStatement[] statements = change.generateRollbackStatements(new MockDatabase()); + Assert.assertEquals(1, statements.length); + Assert.assertTrue(statements[0] instanceof DropColumnStatement); + DropColumnStatement dropStmt = (DropColumnStatement)statements[0]; + Assert.assertEquals("catalog1", dropStmt.getCatalogName()); + Assert.assertEquals("schema1", dropStmt.getSchemaName()); + Assert.assertEquals("table1", dropStmt.getTableName()); + } } From 98430d3d3adba3d2bcf7ab38f0faf3a139f14b3b Mon Sep 17 00:00:00 2001 From: gpsfl <38128299+gpsfl@users.noreply.github.com> Date: Mon, 19 Sep 2022 23:40:59 +0200 Subject: [PATCH 16/23] Fix an error which can occur if getAutoCommit is called on a closed connection (#3135) Check isClosed before calling getAutoCommit --- .../src/main/java/liquibase/database/jvm/JdbcConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liquibase-core/src/main/java/liquibase/database/jvm/JdbcConnection.java b/liquibase-core/src/main/java/liquibase/database/jvm/JdbcConnection.java index 6ecb608b16a..001f886155b 100644 --- a/liquibase-core/src/main/java/liquibase/database/jvm/JdbcConnection.java +++ b/liquibase-core/src/main/java/liquibase/database/jvm/JdbcConnection.java @@ -482,7 +482,7 @@ public void releaseSavepoint(Savepoint savepoint) throws DatabaseException { @Override public void rollback() throws DatabaseException { try { - if (!con.getAutoCommit() && !con.isClosed()) { + if (!con.isClosed() && !con.getAutoCommit()) { con.rollback(); } } catch (SQLException e) { From 6c5e6a9ad5925d630a7db9e4c11a84ab856f44bf Mon Sep 17 00:00:00 2001 From: Saucistophe Date: Mon, 19 Sep 2022 23:41:17 +0200 Subject: [PATCH 17/23] Adjust assembly plugin configuration to allow MacOS builds (fix #2065) (#2136) Attempt to fix #2065 --- liquibase-dist/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/liquibase-dist/pom.xml b/liquibase-dist/pom.xml index 114ebd75e5a..8bfea84c6d6 100644 --- a/liquibase-dist/pom.xml +++ b/liquibase-dist/pom.xml @@ -230,6 +230,7 @@ maven-assembly-plugin 3.4.2 + posix liquibase-${project.version} false false From 895605a33380536cbe1d5e1f8e1d012f64bc9e20 Mon Sep 17 00:00:00 2001 From: MichaelKern-IVV <102645261+MichaelKern-IVV@users.noreply.github.com> Date: Mon, 19 Sep 2022 23:41:28 +0200 Subject: [PATCH 18/23] apply sqlVisitors to CompoundStatements on DB2z (#3220) Co-authored-by: michaelmatthiaskern --- .../liquibase/executor/jvm/JdbcExecutor.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/liquibase-core/src/main/java/liquibase/executor/jvm/JdbcExecutor.java b/liquibase-core/src/main/java/liquibase/executor/jvm/JdbcExecutor.java index e054711a506..553c530e021 100644 --- a/liquibase-core/src/main/java/liquibase/executor/jvm/JdbcExecutor.java +++ b/liquibase-core/src/main/java/liquibase/executor/jvm/JdbcExecutor.java @@ -153,7 +153,7 @@ public void execute(final SqlStatement sql, final List sqlVisitors) } if (sql instanceof CompoundStatement) { if (database instanceof Db2zDatabase) { - executeDb2ZosComplexStatement(sql); + executeDb2ZosComplexStatement(sql, sqlVisitors); return; } } @@ -338,7 +338,7 @@ public void comment(String message) throws DatabaseException { Scope.getCurrentScope().getLog(getClass()).fine(message); } - private void executeDb2ZosComplexStatement(SqlStatement sqlStatement) throws DatabaseException { + private void executeDb2ZosComplexStatement(final SqlStatement sqlStatement, final List sqlVisitors) throws DatabaseException { DatabaseConnection con = database.getConnection(); if (con instanceof OfflineConnection) { @@ -346,12 +346,19 @@ private void executeDb2ZosComplexStatement(SqlStatement sqlStatement) throws Dat } Sql[] sqls = SqlGeneratorFactory.getInstance().generateSql(sqlStatement, database); for (Sql sql : sqls) { + String stmtText = sql.toSql(); + if (sqlVisitors != null) { + for (SqlVisitor visitor : sqlVisitors) { + stmtText = visitor.modifySql(stmtText, database); + } + } + try { if (sql instanceof CallableSql) { CallableStatement call = null; ResultSet resultSet = null; try { - call = ((JdbcConnection) con).getUnderlyingConnection().prepareCall(sql.toSql()); + call = ((JdbcConnection) con).getUnderlyingConnection().prepareCall(stmtText); resultSet = call.executeQuery(); checkCallStatus(resultSet, ((CallableSql) sql).getExpectedStatus()); } finally { @@ -361,11 +368,11 @@ private void executeDb2ZosComplexStatement(SqlStatement sqlStatement) throws Dat Statement stmt = null; try { if (sqlStatement instanceof CompoundStatement) { - stmt = ((JdbcConnection) con).getUnderlyingConnection().prepareStatement(sql.toSql()); + stmt = ((JdbcConnection) con).getUnderlyingConnection().prepareStatement(stmtText); ((PreparedStatement)stmt).execute(); } else { stmt = ((JdbcConnection) con).getUnderlyingConnection().createStatement(); - stmt.execute(sql.toSql()); + stmt.execute(stmtText); } con.commit(); } finally { From 7cf372305bbd7326967befdc54635425ed60b7e0 Mon Sep 17 00:00:00 2001 From: MultiM25 <34519620+MultiM25@users.noreply.github.com> Date: Mon, 19 Sep 2022 23:41:43 +0200 Subject: [PATCH 19/23] Improved message when precondition onFail : MARK_RAN is set (#2238) Solving missleading log message when precondition onFail : MARK_RAN is set --- liquibase-core/src/main/java/liquibase/changelog/ChangeSet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liquibase-core/src/main/java/liquibase/changelog/ChangeSet.java b/liquibase-core/src/main/java/liquibase/changelog/ChangeSet.java index 52ca869dc46..dc29087f150 100644 --- a/liquibase-core/src/main/java/liquibase/changelog/ChangeSet.java +++ b/liquibase-core/src/main/java/liquibase/changelog/ChangeSet.java @@ -616,7 +616,7 @@ public ExecType execute(DatabaseChangeLog databaseChangeLog, ChangeExecListener execType = ExecType.MARK_RAN; skipChange = true; - log.info("Marking ChangeSet: " + toString() + " ran despite precondition failure due to onFail='MARK_RAN': " + message); + log.info("Marking ChangeSet: \"" + toString() + "\" as ran despite precondition failure due to onFail='MARK_RAN': " + message); } else if (preconditions.getOnFail().equals(PreconditionContainer.FailOption.WARN)) { execType = null; //already warned } else { From 0fc0955c5dbd7f8384c2f71277cb65c01f344970 Mon Sep 17 00:00:00 2001 From: Gabriel Nardes Giampietro Date: Mon, 19 Sep 2022 18:42:09 -0300 Subject: [PATCH 20/23] Add default catalog condition to SequenceSnapshotGenerator for Oracle (#3152) Add default catalog condition to SequenceSnapshotGenerator for Oracle (#3138) --- .../jvm/SequenceSnapshotGenerator.java | 6 +++- .../jvm/SequenceSnapshotGeneratorTest.groovy | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 liquibase-core/src/test/groovy/liquibase/snapshot/jvm/SequenceSnapshotGeneratorTest.groovy diff --git a/liquibase-core/src/main/java/liquibase/snapshot/jvm/SequenceSnapshotGenerator.java b/liquibase-core/src/main/java/liquibase/snapshot/jvm/SequenceSnapshotGenerator.java index ea4a905e2b5..7f816d31b63 100644 --- a/liquibase-core/src/main/java/liquibase/snapshot/jvm/SequenceSnapshotGenerator.java +++ b/liquibase-core/src/main/java/liquibase/snapshot/jvm/SequenceSnapshotGenerator.java @@ -194,6 +194,10 @@ protected String getSelectSequenceSql(Schema schema, Database database) { * 12cR1: http://docs.oracle.com/database/121/SQLRF/statements_6017.htm * 11gR2: http://docs.oracle.com/cd/E11882_01/server.112/e41084/statements_6015.htm */ + String catalogName = schema.getCatalogName(); + if (catalogName == null || catalogName.isEmpty()) { + catalogName = database.getDefaultCatalogName(); + } return "SELECT sequence_name, \n" + "CASE WHEN increment_by > 0 \n" + " THEN CASE WHEN min_value=1 THEN NULL ELSE min_value END\n" + @@ -208,7 +212,7 @@ protected String getSelectSequenceSql(Schema schema, Database database) { "CASE WHEN order_flag = 'N' THEN NULL ELSE order_flag END AS is_ordered, \n" + "LAST_NUMBER as START_VALUE, \n" + "CASE WHEN cache_size = 20 THEN NULL ELSE cache_size END AS cache_size \n" + - "FROM ALL_SEQUENCES WHERE SEQUENCE_OWNER = '" + schema.getCatalogName() + "'"; + "FROM ALL_SEQUENCES WHERE SEQUENCE_OWNER = '" + catalogName + "'"; } else if (database instanceof PostgresDatabase) { int version = 9; try { diff --git a/liquibase-core/src/test/groovy/liquibase/snapshot/jvm/SequenceSnapshotGeneratorTest.groovy b/liquibase-core/src/test/groovy/liquibase/snapshot/jvm/SequenceSnapshotGeneratorTest.groovy new file mode 100644 index 00000000000..6872f16d605 --- /dev/null +++ b/liquibase-core/src/test/groovy/liquibase/snapshot/jvm/SequenceSnapshotGeneratorTest.groovy @@ -0,0 +1,36 @@ +package liquibase.snapshot.jvm + +import liquibase.database.Database +import liquibase.database.core.OracleDatabase +import liquibase.structure.core.Schema +import spock.lang.Specification +import spock.lang.Unroll + +class SequenceSnapshotGeneratorTest extends Specification { + + private final static DEFAULT_CATALOG_NAME = "DEFAULT_CATALOG_NAME" + + @Unroll + def "When catalog on changeset is #changesetCatalog, the SEQUENCE_OWNER will be #expectedSequenceOwner"() { + given: + SequenceSnapshotGenerator sequenceSnapshotGenerator = new SequenceSnapshotGenerator() + Database database = Mock(OracleDatabase) + database.getDefaultCatalogName() >> DEFAULT_CATALOG_NAME + Schema schema = Mock(Schema) + schema.getCatalogName() >> changesetCatalog + + when: + String sql = sequenceSnapshotGenerator.getSelectSequenceSql(schema, database) + + then: + String ownerEqualsClause = "SEQUENCE_OWNER = " + String actualSequenceOwner = sql.substring(sql.indexOf(ownerEqualsClause) + ownerEqualsClause.size() + 1, sql.length() - 1) + actualSequenceOwner == expectedSequenceOwner + + where: + changesetCatalog | expectedSequenceOwner + "ANY_STRING" | "ANY_STRING" + "" | DEFAULT_CATALOG_NAME + null | DEFAULT_CATALOG_NAME + } +} From 6ae743e335d54f4931653c946d27b931c62ef95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Kl=C3=A4ger?= Date: Mon, 19 Sep 2022 23:42:30 +0200 Subject: [PATCH 21/23] Fixed issue with h2 loadUpdateData not correctly handling values with the string " values " in the inserted data (#1831) fix greedy subexpression in regex --- .../core/InsertOrUpdateGeneratorH2.java | 3 +-- .../core/InsertOrUpdateGeneratorH2Test.java | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/liquibase-core/src/main/java/liquibase/sqlgenerator/core/InsertOrUpdateGeneratorH2.java b/liquibase-core/src/main/java/liquibase/sqlgenerator/core/InsertOrUpdateGeneratorH2.java index 95430946f1a..d83311a58c2 100644 --- a/liquibase-core/src/main/java/liquibase/sqlgenerator/core/InsertOrUpdateGeneratorH2.java +++ b/liquibase-core/src/main/java/liquibase/sqlgenerator/core/InsertOrUpdateGeneratorH2.java @@ -4,7 +4,6 @@ import liquibase.database.core.H2Database; import liquibase.exception.LiquibaseException; import liquibase.sqlgenerator.SqlGeneratorChain; -import liquibase.sqlgenerator.core.InsertOrUpdateGenerator; import liquibase.statement.core.InsertOrUpdateStatement; import java.util.regex.Matcher; @@ -18,7 +17,7 @@ public boolean supports(InsertOrUpdateStatement statement, Database database) { @Override protected String getInsertStatement(InsertOrUpdateStatement insertOrUpdateStatement, Database database, SqlGeneratorChain sqlGeneratorChain) { String insertStatement = super.getInsertStatement(insertOrUpdateStatement, database, sqlGeneratorChain); - return insertStatement.replaceAll("(?i)insert into (.+) (values .+)", "MERGE INTO $1 KEY(" + Matcher.quoteReplacement(insertOrUpdateStatement.getPrimaryKey()) + ") $2"); + return insertStatement.replaceAll("(?i)insert into (.+?) (values .+)", "MERGE INTO $1 KEY(" + Matcher.quoteReplacement(insertOrUpdateStatement.getPrimaryKey()) + ") $2"); } @Override diff --git a/liquibase-core/src/test/java/liquibase/sqlgenerator/core/InsertOrUpdateGeneratorH2Test.java b/liquibase-core/src/test/java/liquibase/sqlgenerator/core/InsertOrUpdateGeneratorH2Test.java index 21521223732..b76df3ae428 100644 --- a/liquibase-core/src/test/java/liquibase/sqlgenerator/core/InsertOrUpdateGeneratorH2Test.java +++ b/liquibase-core/src/test/java/liquibase/sqlgenerator/core/InsertOrUpdateGeneratorH2Test.java @@ -109,4 +109,31 @@ public void testGenerateSql_notOnlyUpdate() { assertEquals(String.format("MERGE INTO %s.%s (%s, %s) KEY(%s) VALUES ('%s', '%s');", SCHEMA_NAME, TABLE_NAME, "pk1", "col0", "pk1", "keyvalue1", "value0"), results[0].toSql()); } + /** + * Test method for {@link InsertOrUpdateGenerator#generateSql(InsertOrUpdateStatement, Database, SqlGeneratorChain)}. + * Verify that " values " in the column value works. + */ + @Test + public void testGenerateSql_notOnlyUpdate_valuesInColumnValue() { + final InsertOrUpdateGeneratorH2 generator = new InsertOrUpdateGeneratorH2(); + + final InsertOrUpdateStatement insertOrUpdateStatement = new InsertOrUpdateStatement(CATALOG_NAME, SCHEMA_NAME, TABLE_NAME, "pk1"); + final Database database = new H2Database(); + final SqlGeneratorChain sqlGeneratorChain = null; + + ColumnConfig columnConfig; + columnConfig = new ColumnConfig(); + columnConfig.setValue("keyvalue1"); + columnConfig.setName("pk1"); + insertOrUpdateStatement.addColumn(columnConfig); + columnConfig = new ColumnConfig(); + columnConfig.setValue("scale values mean"); + columnConfig.setName("col0"); + insertOrUpdateStatement.addColumn(columnConfig); + + Sql[] results = generator.generateSql(insertOrUpdateStatement, database, sqlGeneratorChain); + assertThat(results, is(arrayWithSize(1))); + assertEquals(String.format("MERGE INTO %s.%s (%s, %s) KEY(%s) VALUES ('%s', '%s');", SCHEMA_NAME, TABLE_NAME, "pk1", "col0", "pk1", "keyvalue1", "scale values mean"), results[0].toSql()); + } + } From e7be0d8fb03d8e4b644f006fb5a00443930eb9f4 Mon Sep 17 00:00:00 2001 From: neilnaveen <42328488+neilnaveen@users.noreply.github.com> Date: Mon, 19 Sep 2022 16:42:50 -0500 Subject: [PATCH 22/23] chore: Set permissions for GitHub actions (#2997) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restrict the GitHub token permissions only to the required ones; this way, even if the attackers will succeed in compromising your workflow, they won’t be able to do much. Signed-off-by: neilnaveen <42328488+neilnaveen@users.noreply.github.com> Signed-off-by: neilnaveen <42328488+neilnaveen@users.noreply.github.com> Co-authored-by: Nathan Voxland --- .github/workflows/build.yml | 2 ++ .github/workflows/create-release.yml | 2 ++ .github/workflows/snyk.yml | 3 +++ 3 files changed, 7 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8bf05fdce66..09a1b51f4cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,8 @@ on: jobs: check_build_safety: + permissions: + contents: none name: Check if Build should be done runs-on: ubuntu-latest steps: diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 8d3865ea853..b20eff337e7 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -96,6 +96,8 @@ jobs: (cd download/repo/liquibase-pro && git push -f origin v${{ needs.setup.outputs.version }}) build-installers: + permissions: + contents: write # for softprops/action-gh-release to create GitHub release needs: [ setup, reversion ] name: Build Installers runs-on: macos-latest #needs macos for apple notarization diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index 4800d839d46..70a617ab7f1 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -8,6 +8,9 @@ on: - cron: "5 6 * * *" workflow_dispatch: +permissions: + contents: read + jobs: security-scan: # This workflow only runs on the main liquibase repo, not in forks From 6c6bb0f9029d18f250a3e349d4faa44cdb67c361 Mon Sep 17 00:00:00 2001 From: Tsvi Zandany <44092058+szandany@users.noreply.github.com> Date: Mon, 19 Sep 2022 16:54:12 -0500 Subject: [PATCH 23/23] Create liquibase_autocomplete.zsh (#3130) --- .../main/archive/lib/liquibase_autocomplete.zsh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 liquibase-dist/src/main/archive/lib/liquibase_autocomplete.zsh diff --git a/liquibase-dist/src/main/archive/lib/liquibase_autocomplete.zsh b/liquibase-dist/src/main/archive/lib/liquibase_autocomplete.zsh new file mode 100644 index 00000000000..321297cad85 --- /dev/null +++ b/liquibase-dist/src/main/archive/lib/liquibase_autocomplete.zsh @@ -0,0 +1,17 @@ +function _liquibase(){ + local state + _arguments '1: :->commands' '2: :->attributes' '3: :->checks_attributes' + + case $state in + commands) + compadd -Q checks --help update updateSQL updateCount updateCountSQL updateToTag updateToTagSQL status registerChangeLog syncHub rollback rollbackSQL 'rollbackOneChangeSet --changeSetAuthor --changeSetId --changeSetPath --force' 'rollbackOneChangeSetSQL --changeSetAuthor --changeSetId --changeSetPath' 'rollbackOneUpdate --deploymentId --force' 'rollbackOneUpdateSQL --deploymentId' rollbackToDate rollbackToDateSQL rollbackCount rollbackCountSQL futureRollbackSQL futureRollbackFromTagSQL updateTestingRollback generateChangeLog snapshot snapshotReference diff diffChangeLog dbDoc history tag tagExists status unexpectedChangeSets validate calculateCheckSum clearCheckSums changelogSync changelogSyncSQL changeLogSyncToTag changeLogSyncToTagSQL markNextChangeSetRan markNextChangeSetRanSQL listLocks releaseLocks dropAll + ;; + attributes) + compadd -Q show run customize enable disable delete reset bulk-set --changeLogFile --force --format --username --password --url --classpath --driver --databaseClass --propertyProviderClass --defaultSchemaName --contexts --labels --defaultsFile --delimiter --driverPropertiesFile --changeExecListenerClass --changeExecListenerPropertiesFile --liquibaseCatalogName --liquibaseSchemaName --databaseChangeLogTableName --databaseChangeLogLockTableName --databaseChangeLogTablespaceName --liquibaseSchemaName --includeSystemClasspath --overwriteOutputFile --promptForNonLocalDatabase --logLevel --logFile --currentDateTimeFunction --outputDefaultSchema --outputDefaultCatalog --outputFile --rollbackScript --excludeObjects --includeObjects --help --version --snapshotFormat --referenceUsername --referencePassword --referenceUrl --defaultCatalogName --defaultSchemaName --referenceDefaultCatalogName --referenceDefaultSchemaName --schemas --referenceSchemas --outputSchemaAs --includeCatalog --includeSchema --includeTablespace --referenceDriver --dataOutputDirectory --diffTypes --diffTypes=catalog,tables,functions,views,columns,indexes,foreignkeys,primarykeys,uniqueconstraints,data,storedprocedure,triggers,sequences --verbose --liquibaseProLicenseKey + ;; + checks_attributes) + compadd -Q --dummy --check-name --checks-settings-file --format + ;; + esac +} +compdef _liquibase liquibase