Add db-migrations-flyway-liquibase: Flyway vs Liquibase migrations, rollbacks and baselines on Spring Boot 4.1
Companion code for the Flyway vs Liquibase article: checksum validation, out-of-order and repeatable migrations, baselining an existing schema, Flyway Community's undo/diff/deploy stubs, concurrent-startup locking for both tools, Liquibase changeset identity and rollback (auto-generated vs explicit), a verified Liquibase 5.0.3 filename-caching defect, the new OSS license service, the FSL license change, and running both tools against one database. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Q6XdRjtsp4862EM44T7i9a
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
package com.ankurm.dbmigrations.liquibase;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import liquibase.Contexts;
|
||||
import liquibase.Liquibase;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import com.ankurm.dbmigrations.DbDump;
|
||||
import com.ankurm.dbmigrations.Transcript;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Liquibase's answer to {@code FlywayConcurrentMigrateTest}: a single-row lock table,
|
||||
* {@code DATABASECHANGELOGLOCK}, that a real second {@code update()} call has to wait for.
|
||||
* <p>
|
||||
* Two Liquibase instances racing to create {@code DATABASECHANGELOG}/{@code DATABASECHANGELOGLOCK}
|
||||
* for the very first time is a real, separate failure mode from the lock contention this test is
|
||||
* about — the loser gets a plain {@code DatabaseException} ("table already exists"), because the
|
||||
* lock table that would make it wait gracefully doesn't exist yet either. So a bootstrap step runs
|
||||
* first, on its own {@link Liquibase} instance, against an empty changelog, purely to get those two
|
||||
* tracking tables created before the real race starts.
|
||||
* <p>
|
||||
* That bootstrap changelog file is deliberately named {@code bootstrap-only.yaml}, not
|
||||
* {@code master.yaml} — reusing the same filename for the bootstrap and the real changelog (even
|
||||
* from a different directory, via a different {@code DirectoryResourceAccessor}) reliably
|
||||
* reproduced a genuine Liquibase 5.0.3 defect: both real instances would log a normal
|
||||
* {@code Run: 1} / "successful" summary, but the changeset's own code never ran and
|
||||
* {@code DATABASECHANGELOG} stayed empty — a phantom success caused by something in Liquibase's
|
||||
* changelog-history handling keying off the changelog's simple filename rather than the full
|
||||
* resource path. Verified by toggling only the bootstrap file's name with everything else held
|
||||
* constant: same name reproduces it every time, a distinct name never does. See
|
||||
* docs/11-liquibase-locking.md.
|
||||
*/
|
||||
class LiquibaseConcurrentUpdateTest {
|
||||
|
||||
@Test
|
||||
void twoInstancesUpdatingAtOnceAreSerializedNotDuplicated(@TempDir Path tmp) throws Exception {
|
||||
Path changelogDir = tmp.resolve("changelog");
|
||||
Files.createDirectories(changelogDir);
|
||||
Files.writeString(changelogDir.resolve("master.yaml"), """
|
||||
databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 1-slow-change
|
||||
author: ankurm
|
||||
changes:
|
||||
- customChange:
|
||||
class: com.ankurm.dbmigrations.liquibase.SlowCustomChange
|
||||
""");
|
||||
|
||||
Path bootstrapChangelogDir = tmp.resolve("bootstrap-changelog");
|
||||
Files.createDirectories(bootstrapChangelogDir);
|
||||
Files.writeString(bootstrapChangelogDir.resolve("bootstrap-only.yaml"), "databaseChangeLog: []\n");
|
||||
|
||||
String url = "jdbc:h2:file:" + tmp.resolve("liquibase-concurrent") + ";AUTO_SERVER=TRUE";
|
||||
|
||||
// Bootstrap DATABASECHANGELOG / DATABASECHANGELOGLOCK first, on a changelog file with a
|
||||
// name distinct from the real one (see the class javadoc for why that distinction matters).
|
||||
try (Liquibase bootstrap = LiquibaseTestSupport.open(url, bootstrapChangelogDir, "bootstrap-only.yaml")) {
|
||||
bootstrap.update(new Contexts());
|
||||
}
|
||||
|
||||
ExecutorService pool = Executors.newFixedThreadPool(2);
|
||||
Instant start = Instant.now();
|
||||
try {
|
||||
Future<Long> instanceA = pool.submit(() -> runUpdate(url, changelogDir));
|
||||
Future<Long> instanceB = pool.submit(() -> runUpdate(url, changelogDir));
|
||||
long millisA = instanceA.get();
|
||||
long millisB = instanceB.get();
|
||||
Duration total = Duration.between(start, Instant.now());
|
||||
|
||||
Transcript t = Transcript.start("13-liquibase-lock-contention",
|
||||
"Liquibase: two instances calling update() at the same moment");
|
||||
t.line("instance A update() took " + millisA + "ms");
|
||||
t.line("instance B update() took " + millisB + "ms");
|
||||
t.line("wall-clock time for both, run concurrently: " + total.toMillis() + "ms");
|
||||
|
||||
try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
|
||||
t.section("databasechangeloglock after both finished");
|
||||
t.line(DbDump.table(conn, "select id, locked, lockedby from databasechangeloglock"));
|
||||
t.section("databasechangelog after both finished");
|
||||
String log = DbDump.table(conn, "select id, author, exectype from databasechangelog");
|
||||
t.line(log);
|
||||
assertThat(log.lines().filter(l -> l.contains("1-slow-change")).count())
|
||||
.as("the slow changeset must run exactly once despite two concurrent update() calls")
|
||||
.isEqualTo(1);
|
||||
}
|
||||
t.write();
|
||||
|
||||
// Exactly one of the two calls does the real 800ms of work; the other blocks on
|
||||
// DATABASECHANGELOGLOCK for roughly that same window before finding nothing left to
|
||||
// do. That means the wall clock for both together is close to ONE migration's
|
||||
// duration, not their sum — so the direct proof of serialization is that whichever
|
||||
// call "lost" the race still took nearly as long as the winner, instead of returning
|
||||
// near-instantly the way an unlocked, do-nothing update() call would.
|
||||
assertThat(millisA).isGreaterThan(600);
|
||||
assertThat(millisB).isGreaterThan(600);
|
||||
}
|
||||
finally {
|
||||
pool.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private static long runUpdate(String url, Path changelogDir) throws Exception {
|
||||
Instant t0 = Instant.now();
|
||||
try (Liquibase liquibase = LiquibaseTestSupport.open(url, changelogDir, "master.yaml")) {
|
||||
liquibase.update(new Contexts());
|
||||
}
|
||||
return Duration.between(t0, Instant.now()).toMillis();
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.ankurm.dbmigrations.liquibase;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
|
||||
import liquibase.Liquibase;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import com.ankurm.dbmigrations.DbDump;
|
||||
import com.ankurm.dbmigrations.Transcript;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* The Liquibase equivalent of {@code FlywayHappyPathTest}: two changesets, applied once. See
|
||||
* docs/09-liquibase-anatomy-of-an-update.md.
|
||||
*/
|
||||
class LiquibaseHappyPathTest {
|
||||
|
||||
@Test
|
||||
void appliesChangeSetsInOrder(@TempDir Path tmp) throws Exception {
|
||||
Path changelogDir = tmp.resolve("changelog");
|
||||
Files.createDirectories(changelogDir);
|
||||
Files.writeString(changelogDir.resolve("master.yaml"), """
|
||||
databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 1-create-account
|
||||
author: ankurm
|
||||
changes:
|
||||
- createTable:
|
||||
tableName: account
|
||||
columns:
|
||||
- column: {name: id, type: bigint, autoIncrement: true, constraints: {primaryKey: true}}
|
||||
- column: {name: owner, type: varchar(100), constraints: {nullable: false}}
|
||||
- changeSet:
|
||||
id: 2-seed-account
|
||||
author: ankurm
|
||||
changes:
|
||||
- insert:
|
||||
tableName: account
|
||||
columns:
|
||||
- {column: {name: owner, value: 'Katherine Johnson'}}
|
||||
""");
|
||||
|
||||
String url = "jdbc:h2:file:" + tmp.resolve("liquibase-happy-path");
|
||||
Transcript t = Transcript.start("09-liquibase-happy-path", "Liquibase: two changesets, applied on startup");
|
||||
|
||||
Liquibase liquibase = LiquibaseTestSupport.open(url, changelogDir, "master.yaml");
|
||||
liquibase.update();
|
||||
|
||||
try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
|
||||
t.section("databasechangelog");
|
||||
String log = DbDump.table(conn,
|
||||
"select id, author, filename, orderexecuted, exectype from databasechangelog order by orderexecuted");
|
||||
t.line(log);
|
||||
assertThat(log).contains("1-create-account").contains("2-seed-account");
|
||||
|
||||
t.section("account table");
|
||||
String rows = DbDump.table(conn, "select id, owner from account");
|
||||
t.line(rows);
|
||||
assertThat(rows).contains("Katherine Johnson");
|
||||
}
|
||||
t.write();
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.ankurm.dbmigrations.liquibase;
|
||||
|
||||
import liquibase.Scope;
|
||||
import liquibase.license.LicenseServiceFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.ankurm.dbmigrations.Transcript;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Liquibase Community 5.0 shipped a {@code liquibase.license} package inside {@code liquibase-core}
|
||||
* itself — new since the Functional Source License change (docs/13-the-fsl-license-change.md).
|
||||
* This test asks the actual service Spring Boot's {@code LiquibaseAutoConfiguration} runs on top
|
||||
* of whether it is licensed, rather than repeating what the release notes say. See
|
||||
* docs/12-the-oss-license-service.md.
|
||||
*/
|
||||
class LiquibaseLicenseServiceTest {
|
||||
|
||||
@Test
|
||||
void theOssServiceReportsNoLicenseAndDoesNotFail() {
|
||||
var service = Scope.getCurrentScope().getSingleton(LicenseServiceFactory.class).getLicenseService();
|
||||
|
||||
Transcript t = Transcript.start("14-liquibase-oss-license-service",
|
||||
"Liquibase: the LicenseService actually wired up on the classpath Boot uses");
|
||||
t.line("implementation: " + service.getClass().getName());
|
||||
t.line("licenseIsInstalled(): " + service.licenseIsInstalled());
|
||||
t.line("licenseIsValid(\"any\"): " + service.licenseIsValid("any"));
|
||||
t.line("getLicenseInfo(): \"" + service.getLicenseInfo() + "\"");
|
||||
t.write();
|
||||
|
||||
assertThat(service.getClass().getSimpleName()).isEqualTo("OSSLicenseService");
|
||||
assertThat(service.licenseIsInstalled()).isFalse();
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.ankurm.dbmigrations.liquibase;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import liquibase.Contexts;
|
||||
import liquibase.LabelExpression;
|
||||
import liquibase.Liquibase;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import com.ankurm.dbmigrations.DbDump;
|
||||
import com.ankurm.dbmigrations.Transcript;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* {@code createTable} is one of the change types Liquibase can invert on its own — it just runs
|
||||
* {@code DROP TABLE}. No {@code <rollback>} block was written anywhere in this changelog.
|
||||
* See docs/10-rollback-auto-generated-vs-explicit.md.
|
||||
*/
|
||||
class LiquibaseRollbackAutoTest {
|
||||
|
||||
@Test
|
||||
void rollingBackACreateTableChangeSetNeedsNoExplicitRollback(@TempDir Path tmp) throws Exception {
|
||||
Path changelogDir = tmp.resolve("changelog");
|
||||
Files.createDirectories(changelogDir);
|
||||
Files.writeString(changelogDir.resolve("master.yaml"), """
|
||||
databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 1-create-session
|
||||
author: ankurm
|
||||
changes:
|
||||
- createTable:
|
||||
tableName: session
|
||||
columns:
|
||||
- column: {name: id, type: bigint, autoIncrement: true, constraints: {primaryKey: true}}
|
||||
- column: {name: token, type: varchar(64)}
|
||||
""");
|
||||
|
||||
String url = "jdbc:h2:file:" + tmp.resolve("rollback-auto");
|
||||
Transcript t = Transcript.start("10-liquibase-rollback-auto", "Liquibase: auto-generated rollback for createTable");
|
||||
|
||||
try (Liquibase liquibase = LiquibaseTestSupport.open(url, changelogDir, "master.yaml")) {
|
||||
liquibase.update();
|
||||
}
|
||||
t.line("after update(): session table exists = " + tableExists(url, "SESSION"));
|
||||
|
||||
try (Liquibase liquibase = LiquibaseTestSupport.open(url, changelogDir, "master.yaml")) {
|
||||
liquibase.rollback(1, new Contexts(), new LabelExpression());
|
||||
}
|
||||
boolean existsAfterRollback = tableExists(url, "SESSION");
|
||||
t.line("after rollback(1): session table exists = " + existsAfterRollback);
|
||||
|
||||
try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
|
||||
t.section("databasechangelog after rollback");
|
||||
t.line(DbDump.table(conn, "select id, exectype from databasechangelog"));
|
||||
}
|
||||
t.write();
|
||||
|
||||
assertThat(existsAfterRollback).isFalse();
|
||||
}
|
||||
|
||||
private static boolean tableExists(String url, String tableName) throws SQLException {
|
||||
try (Connection conn = DriverManager.getConnection(url, "sa", "");
|
||||
var rs = conn.getMetaData().getTables(null, "PUBLIC", tableName, null)) {
|
||||
return rs.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.ankurm.dbmigrations.liquibase;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
|
||||
import liquibase.Contexts;
|
||||
import liquibase.LabelExpression;
|
||||
import liquibase.Liquibase;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import com.ankurm.dbmigrations.DbDump;
|
||||
import com.ankurm.dbmigrations.Transcript;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* The same {@code insert} changeset as {@link LiquibaseRollbackFailTest}, this time with an
|
||||
* explicit {@code rollback:} block — the fix for that failure, not just a description of it.
|
||||
* See docs/10-rollback-auto-generated-vs-explicit.md.
|
||||
*/
|
||||
class LiquibaseRollbackExplicitTest {
|
||||
|
||||
@Test
|
||||
void anExplicitRollbackBlockMakesTheSameInsertReversible(@TempDir Path tmp) throws Exception {
|
||||
Path changelogDir = tmp.resolve("changelog");
|
||||
Files.createDirectories(changelogDir);
|
||||
Files.writeString(changelogDir.resolve("master.yaml"), """
|
||||
databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 1-create-audit-log
|
||||
author: ankurm
|
||||
changes:
|
||||
- createTable:
|
||||
tableName: audit_log
|
||||
columns:
|
||||
- column: {name: id, type: bigint, autoIncrement: true, constraints: {primaryKey: true}}
|
||||
- column: {name: event, type: varchar(100)}
|
||||
- changeSet:
|
||||
id: 2-seed-audit-log
|
||||
author: ankurm
|
||||
changes:
|
||||
- insert:
|
||||
tableName: audit_log
|
||||
columns:
|
||||
- {column: {name: event, value: 'system-start'}}
|
||||
rollback:
|
||||
- delete:
|
||||
tableName: audit_log
|
||||
where: event='system-start'
|
||||
""");
|
||||
|
||||
String url = "jdbc:h2:file:" + tmp.resolve("rollback-explicit");
|
||||
Transcript t = Transcript.start("12-liquibase-rollback-explicit",
|
||||
"Liquibase: the same insert, now with an explicit rollback block");
|
||||
|
||||
Liquibase liquibase = LiquibaseTestSupport.open(url, changelogDir, "master.yaml");
|
||||
liquibase.update();
|
||||
try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
|
||||
t.section("audit_log right after update()");
|
||||
t.line(DbDump.table(conn, "select id, event from audit_log"));
|
||||
}
|
||||
|
||||
Liquibase forRollback = LiquibaseTestSupport.open(url, changelogDir, "master.yaml");
|
||||
forRollback.rollback(1, new Contexts(), new LabelExpression());
|
||||
|
||||
try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
|
||||
t.section("audit_log after rollback(1) — table still exists, the row is gone");
|
||||
String rows = DbDump.table(conn, "select id, event from audit_log");
|
||||
t.line(rows);
|
||||
assertThat(rows).contains("(0 rows)");
|
||||
}
|
||||
t.write();
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.ankurm.dbmigrations.liquibase;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import liquibase.Contexts;
|
||||
import liquibase.LabelExpression;
|
||||
import liquibase.Liquibase;
|
||||
import liquibase.exception.LiquibaseException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import com.ankurm.dbmigrations.Transcript;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* {@code insert} is not in the list of change types Liquibase can invert automatically. With no
|
||||
* {@code <rollback>} block, rolling it back fails — for real, not as a documented limitation
|
||||
* taken on faith. See docs/10-rollback-auto-generated-vs-explicit.md.
|
||||
*/
|
||||
class LiquibaseRollbackFailTest {
|
||||
|
||||
@Test
|
||||
void rollingBackAnInsertWithNoExplicitRollbackFails(@TempDir Path tmp) throws Exception {
|
||||
Path changelogDir = tmp.resolve("changelog");
|
||||
Files.createDirectories(changelogDir);
|
||||
Files.writeString(changelogDir.resolve("master.yaml"), """
|
||||
databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 1-create-audit-log
|
||||
author: ankurm
|
||||
changes:
|
||||
- createTable:
|
||||
tableName: audit_log
|
||||
columns:
|
||||
- column: {name: id, type: bigint, autoIncrement: true, constraints: {primaryKey: true}}
|
||||
- column: {name: event, type: varchar(100)}
|
||||
- changeSet:
|
||||
id: 2-seed-audit-log
|
||||
author: ankurm
|
||||
changes:
|
||||
- insert:
|
||||
tableName: audit_log
|
||||
columns:
|
||||
- {column: {name: event, value: 'system-start'}}
|
||||
""");
|
||||
|
||||
String url = "jdbc:h2:file:" + tmp.resolve("rollback-fail");
|
||||
Transcript t = Transcript.start("11-liquibase-rollback-no-inverse",
|
||||
"Liquibase: rolling back an insert changeset with no <rollback> block");
|
||||
|
||||
Liquibase liquibase = LiquibaseTestSupport.open(url, changelogDir, "master.yaml");
|
||||
liquibase.update();
|
||||
t.line("after update(): both changesets applied");
|
||||
|
||||
Liquibase forRollback = LiquibaseTestSupport.open(url, changelogDir, "master.yaml");
|
||||
LiquibaseException failure = catchRollbackFailure(forRollback);
|
||||
t.line("rollback(1) threw: " + failure.getClass().getName());
|
||||
t.line("message: " + failure.getMessage());
|
||||
t.write();
|
||||
|
||||
assertThat(failure.getMessage()).containsIgnoringCase("no inverse");
|
||||
}
|
||||
|
||||
private static LiquibaseException catchRollbackFailure(Liquibase liquibase) {
|
||||
try {
|
||||
liquibase.rollback(1, new Contexts(), new LabelExpression());
|
||||
}
|
||||
catch (LiquibaseException ex) {
|
||||
return ex;
|
||||
}
|
||||
throw new AssertionError("expected rollback(1) to fail for a changeset with no explicit rollback");
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.ankurm.dbmigrations.liquibase;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
|
||||
import liquibase.Liquibase;
|
||||
import liquibase.database.Database;
|
||||
import liquibase.database.DatabaseFactory;
|
||||
import liquibase.database.jvm.JdbcConnection;
|
||||
import liquibase.resource.DirectoryResourceAccessor;
|
||||
|
||||
/** Wires up the classic {@link liquibase.Liquibase} facade against a filesystem changelog — no Spring involved. */
|
||||
final class LiquibaseTestSupport {
|
||||
|
||||
private LiquibaseTestSupport() {
|
||||
}
|
||||
|
||||
static Liquibase open(String jdbcUrl, Path changelogDir, String changelogFile) throws Exception {
|
||||
Connection conn = DriverManager.getConnection(jdbcUrl, "sa", "");
|
||||
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(conn));
|
||||
return new Liquibase(changelogFile, new DirectoryResourceAccessor(changelogDir), database);
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.ankurm.dbmigrations.liquibase;
|
||||
|
||||
import liquibase.change.custom.CustomTaskChange;
|
||||
import liquibase.database.Database;
|
||||
import liquibase.database.jvm.JdbcConnection;
|
||||
import liquibase.exception.CustomChangeException;
|
||||
import liquibase.exception.SetupException;
|
||||
import liquibase.exception.ValidationErrors;
|
||||
import liquibase.resource.ResourceAccessor;
|
||||
|
||||
/**
|
||||
* A deliberately slow {@code customChange}, used only by {@link LiquibaseConcurrentUpdateTest} to
|
||||
* widen the window in which a second {@code update()} can try to run against the same database.
|
||||
*/
|
||||
public class SlowCustomChange implements CustomTaskChange {
|
||||
|
||||
@Override
|
||||
public void execute(Database database) throws CustomChangeException {
|
||||
try {
|
||||
JdbcConnection conn = (JdbcConnection) database.getConnection();
|
||||
try (var st = conn.createStatement()) {
|
||||
st.execute("create table slow_changeset_marker (id int primary key)");
|
||||
}
|
||||
Thread.sleep(800);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new CustomChangeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfirmationMessage() {
|
||||
return "slow custom change applied";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws SetupException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFileOpener(ResourceAccessor resourceAccessor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationErrors validate(Database database) {
|
||||
return new ValidationErrors();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user