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/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 }}" diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index fada4b77991..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 @@ -39,7 +42,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 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. 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 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/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/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/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 { 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)); } 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) { 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/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..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; } } @@ -319,7 +319,7 @@ public SqlStatement getStatement() { * @see ColumnMapRowMapper */ protected RowMapper getColumnMapRowMapper() { - return new ColumnMapRowMapper(); + return new ColumnMapRowMapper(database.isCaseSensitive()); } /** @@ -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 { 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); 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/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/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)) }; } 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/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/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)" } 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/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 + } +} 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()); + } } 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()); + } + } 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 diff --git a/liquibase-dist/pom.xml b/liquibase-dist/pom.xml index 7d4a6a57abc..11dbe2b3362 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 @@ -159,7 +159,7 @@ org.liquibase.ext liquibase-sdk-maven-plugin - 0.9.1 + 0.10.18 @@ -230,6 +230,7 @@ maven-assembly-plugin 3.4.2 + posix liquibase-${project.version} false false 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 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-* - + diff --git a/pom.xml b/pom.xml index 00abfb4ea3c..32da1951d24 100644 --- a/pom.xml +++ b/pom.xml @@ -118,13 +118,13 @@ org.mockito mockito-core - 4.7.0 + 4.8.0 test org.mockito mockito-inline - 4.7.0 + 4.8.0 test @@ -227,7 +227,7 @@ net.snowflake snowflake-jdbc - 3.13.21 + 3.13.22