From 2343e3c53dfa6802b50b4db7e1adfb3642374bac Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 19 Nov 2024 14:08:29 +0100 Subject: [PATCH] [jOOQ/jOOQ#9506] More work on Migrations API: - Infer JDBC driver from JDBC URL if not given explicitly - Better FileData debugging info - Add HistoryVersion type to add a migratedAt() Instant to a Version - Add a CleanMojo - Improve mojo Javadocs - Rename init node to root - Make root a reserved ID --- .../migrations/maven/AbstractMigrateMojo.java | 10 +- .../maven/AbstractMigrationsMojo.java | 21 ++++- .../org/jooq/migrations/maven/CleanMojo.java | 72 +++++++++++++++ .../jooq/migrations/maven/HistoryMojo.java | 92 +++++++++++++++++++ .../org/jooq/migrations/maven/LogMojo.java | 15 ++- .../jooq/migrations/maven/MigrateMojo.java | 2 +- .../jooq/migrations/maven/ResolveMojo.java | 2 +- .../org/jooq/migrations/maven/VerifyMojo.java | 2 +- jOOQ/src/main/java/org/jooq/Commit.java | 2 +- jOOQ/src/main/java/org/jooq/History.java | 6 +- .../main/java/org/jooq/HistoryVersion.java | 70 ++++++++++++++ jOOQ/src/main/java/org/jooq/Node.java | 11 ++- .../main/java/org/jooq/impl/AbstractNode.java | 3 +- .../main/java/org/jooq/impl/CommitImpl.java | 7 +- .../main/java/org/jooq/impl/CommitsImpl.java | 25 ++++- .../main/java/org/jooq/impl/HistoryImpl.java | 71 +++++++++++--- .../java/org/jooq/impl/MigrationImpl.java | 11 ++- .../java/org/jooq/impl/MigrationsImpl.java | 3 +- 18 files changed, 385 insertions(+), 40 deletions(-) create mode 100644 jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/CleanMojo.java create mode 100644 jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/HistoryMojo.java create mode 100644 jOOQ/src/main/java/org/jooq/HistoryVersion.java diff --git a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AbstractMigrateMojo.java b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AbstractMigrateMojo.java index a2eb59e7ef..38f975e3e1 100644 --- a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AbstractMigrateMojo.java +++ b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AbstractMigrateMojo.java @@ -79,11 +79,11 @@ public abstract class AbstractMigrateMojo extends AbstractMigrationsMojo { .migrations() .migrateTo(commits.latest()); - if (getLog().isInfoEnabled()) { - getLog().info("Migration from version: " + migration.from()); - getLog().info("Migration to version : " + migration.to()); - getLog().info("Migration queries : " + migration.queries().queries().length); - } + if (getLog().isInfoEnabled()) + getLog().info( + "Migration loaded from version " + migration.from() + " to version " + migration.to() + + " (number of queries: " + migration.queries().queries().length + ")" + ); execute1(migration); } diff --git a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AbstractMigrationsMojo.java b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AbstractMigrationsMojo.java index 5372bd64bd..aeea98a49e 100644 --- a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AbstractMigrationsMojo.java +++ b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AbstractMigrationsMojo.java @@ -49,7 +49,8 @@ import org.jooq.CloseableDSLContext; import org.jooq.Configuration; import org.jooq.conf.MigrationSchema; import org.jooq.impl.DSL; -import org.jooq.tools.StringUtils; +import org.jooq.tools.ClassUtils; +import org.jooq.tools.jdbc.JDBCUtils; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -160,11 +161,14 @@ abstract class AbstractMigrationsMojo extends AbstractMojo { // [#2886] Add the surrounding project's dependencies to the current classloader Thread.currentThread().setContextClassLoader(pluginClassLoader); + project.getRuntimeClasspathElements().forEach(System.out::println); + if (jdbc == null || jdbc.url == null) throw new MojoExecutionException("JDBC URL is required"); - if (jdbc.driver != null) - Class.forName(jdbc.driver); + String driver = driverClass(jdbc); + if (driver != null) + ClassUtils.loadClass(driver).getConstructor().newInstance(); try (CloseableDSLContext ctx = DSL.using(jdbc.url, defaultIfNull(jdbc.user, jdbc.username), jdbc.password)) { @@ -227,6 +231,17 @@ abstract class AbstractMigrationsMojo extends AbstractMojo { } } + private String driverClass(Jdbc j) { + String result = j.driver; + + if (result == null) { + result = JDBCUtils.driver(j.url); + getLog().info("Inferring driver " + result + " from URL " + j.url); + } + + return result; + } + abstract void execute0(Configuration configuration) throws Exception; private URLClassLoader getClassLoader() throws MojoExecutionException { diff --git a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/CleanMojo.java b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/CleanMojo.java new file mode 100644 index 0000000000..4b25a5b692 --- /dev/null +++ b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/CleanMojo.java @@ -0,0 +1,72 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * Apache-2.0 license and offer limited warranties, support, maintenance, and + * commercial database integrations. + * + * For more information, please visit: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.migrations.maven; + +import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES; +import static org.apache.maven.plugins.annotations.ResolutionScope.TEST; + +import org.jooq.Commit; +import org.jooq.Files; +import org.jooq.Migration; +import org.jooq.Queries; +import org.jooq.Query; +import org.jooq.Version; +import org.jooq.tools.StringUtils; + +import org.apache.maven.plugins.annotations.Mojo; + +/** + * Clean the configured schemas, dropping all objects. + * + * @author Lukas Eder + */ +@Mojo( + name = "clean", + defaultPhase = GENERATE_SOURCES, + requiresDependencyResolution = TEST, + threadSafe = true +) +public class CleanMojo extends AbstractMigrateMojo { + + @Override + final void execute1(Migration migration) throws Exception { + Commit root = migration.to().root(); + root.settings().setMigrationAllowsUndo(true); + migration.dsl().migrations().migrateTo(root).execute(); + } +} diff --git a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/HistoryMojo.java b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/HistoryMojo.java new file mode 100644 index 0000000000..da4a6ef6ac --- /dev/null +++ b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/HistoryMojo.java @@ -0,0 +1,92 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * Apache-2.0 license and offer limited warranties, support, maintenance, and + * commercial database integrations. + * + * For more information, please visit: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.migrations.maven; + +import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES; +import static org.apache.maven.plugins.annotations.ResolutionScope.TEST; +import static org.jooq.tools.StringUtils.isEmpty; + +import java.time.Instant; + +import org.jooq.HistoryVersion; +import org.jooq.Migration; +import org.jooq.Version; +import org.jooq.tools.StringUtils; + +import org.apache.maven.plugins.annotations.Mojo; + +/** + * Show the history of currently installed versions on the connected database. + * + * @author Lukas Eder + */ +@Mojo( + name = "history", + defaultPhase = GENERATE_SOURCES, + requiresDependencyResolution = TEST, + threadSafe = true +) +public class HistoryMojo extends AbstractMigrateMojo { + + @Override + final void execute1(Migration migration) throws Exception { + if (getLog().isInfoEnabled()) { + for (HistoryVersion version : migration.dsl().migrations().history()) { + getLog().info(string(version.migratedAt()) + " - Version: " + string(version.version())); + + if (version.version().parents().size() > 1) { + getLog().info(" Merged parents: "); + + for (Version p : version.version().parents()) + getLog().info(" - " + string(p)); + } + } + } + } + + private final String string(Instant instant) { + if (instant == null) + return "0000-00-00T00:00:00.000Z"; + else + return StringUtils.rightPad(instant.toString(), 24); + } + + private final String string(Version version) { + return version.id() + (!isEmpty(version.message()) ? " (" + version.message() + ")" : ""); + } +} diff --git a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/LogMojo.java b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/LogMojo.java index 97883cdb74..01c75d7d47 100644 --- a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/LogMojo.java +++ b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/LogMojo.java @@ -44,10 +44,11 @@ import org.jooq.Migration; import org.jooq.Query; import org.jooq.tools.StringUtils; +import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugins.annotations.Mojo; /** - * The jOOQ Migrations migrate mojo + * Log the queries of the outstanding migration. * * @author Lukas Eder */ @@ -63,10 +64,14 @@ public class LogMojo extends AbstractMigrateMojo { final void execute1(Migration migration) throws Exception { if (getLog().isInfoEnabled()) { Query[] queries = migration.queries().queries(); - int pad = ("" + queries.length).length(); - - for (int i = 0; i < queries.length; i++) - getLog().info(" Query " + StringUtils.leftPad("" + (i + 1), pad) + ": " + queries[i]); + log(getLog(), queries); } } + + static final void log(Log log, Query[] queries) { + int pad = ("" + queries.length).length(); + + for (int i = 0; i < queries.length; i++) + log.info(" Query " + StringUtils.leftPad("" + (i + 1), pad) + ": " + queries[i]); + } } diff --git a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/MigrateMojo.java b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/MigrateMojo.java index ce2ceb33e0..89ff5f2ebb 100644 --- a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/MigrateMojo.java +++ b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/MigrateMojo.java @@ -45,7 +45,7 @@ import org.jooq.Migration; import org.apache.maven.plugins.annotations.Mojo; /** - * The jOOQ Migrations migrate mojo + * Run a migration. * * @author Lukas Eder */ diff --git a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/ResolveMojo.java b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/ResolveMojo.java index 7bd45d7c59..e984000814 100644 --- a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/ResolveMojo.java +++ b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/ResolveMojo.java @@ -45,7 +45,7 @@ import org.jooq.Configuration; import org.apache.maven.plugins.annotations.Mojo; /** - * The jOOQ Migrations resolve mojo + * Mark an outstanding migration as resolved. * * @author Lukas Eder */ diff --git a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/VerifyMojo.java b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/VerifyMojo.java index 600f7a8d41..342be27db7 100644 --- a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/VerifyMojo.java +++ b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/VerifyMojo.java @@ -45,7 +45,7 @@ import org.jooq.Migration; import org.apache.maven.plugins.annotations.Mojo; /** - * The jOOQ Migrations verify mojo + * Verify an outstanding migration. * * @author Lukas Eder */ diff --git a/jOOQ/src/main/java/org/jooq/Commit.java b/jOOQ/src/main/java/org/jooq/Commit.java index 17dce2b7ce..285f934c01 100644 --- a/jOOQ/src/main/java/org/jooq/Commit.java +++ b/jOOQ/src/main/java/org/jooq/Commit.java @@ -39,8 +39,8 @@ package org.jooq; import java.util.Collection; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.ApiStatus.Experimental; +import org.jetbrains.annotations.NotNull; /** * A change set describing the exact migration path between the diff --git a/jOOQ/src/main/java/org/jooq/History.java b/jOOQ/src/main/java/org/jooq/History.java index 77fe949235..5bb0b3a0c7 100644 --- a/jOOQ/src/main/java/org/jooq/History.java +++ b/jOOQ/src/main/java/org/jooq/History.java @@ -52,7 +52,7 @@ import org.jetbrains.annotations.NotNull; * @author Lukas Eder */ @Experimental -public interface History extends Iterable, Scope { +public interface History extends Iterable, Scope { /** * The root {@link Version}. @@ -69,7 +69,7 @@ public interface History extends Iterable, Scope { */ @NotNull @Experimental - Version root() throws DataMigrationVerificationException; + HistoryVersion root() throws DataMigrationVerificationException; /** * The currently installed {@link Version}. @@ -82,7 +82,7 @@ public interface History extends Iterable, Scope { */ @NotNull @Experimental - Version current(); + HistoryVersion current(); /** * Resolve any previous failures in the {@link History}. diff --git a/jOOQ/src/main/java/org/jooq/HistoryVersion.java b/jOOQ/src/main/java/org/jooq/HistoryVersion.java new file mode 100644 index 0000000000..1933a65aa8 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/HistoryVersion.java @@ -0,0 +1,70 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * Apache-2.0 license and offer limited warranties, support, maintenance, and + * commercial database integrations. + * + * For more information, please visit: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq; + +import java.time.Instant; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A {@link Version} in the context of a {@link History}. + */ +public interface HistoryVersion { + + /** + * The version. + */ + @NotNull + Version version(); + + /** + * The history creating this {@link HistoryVersion}. + */ + @NotNull + History history(); + + /** + * The time when this {@link HistoryVersion} was migrated to in the context + * of the {@link #history()}. + *

+ * This is null for the {@link History#root()} version. + */ + @Nullable + Instant migratedAt(); +} diff --git a/jOOQ/src/main/java/org/jooq/Node.java b/jOOQ/src/main/java/org/jooq/Node.java index 10fd1e016a..8944aaaf22 100644 --- a/jOOQ/src/main/java/org/jooq/Node.java +++ b/jOOQ/src/main/java/org/jooq/Node.java @@ -39,21 +39,26 @@ package org.jooq; import java.util.List; +import org.jetbrains.annotations.ApiStatus.Experimental; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.ApiStatus.Experimental; /** * An abstraction over directed, acyclic graph models. *

- * Examples of such models are {@link Version} / {@link Versions} or - * {@link Commit} / {@link Commits}. + * Examples of such models are {@link Version} or {@link Commit} / + * {@link Commits}. * * @author Lukas Eder */ @Experimental public interface Node> extends Scope { + /** + * The name of the {@link #root()} node. + */ + String ROOT = "root"; + /** * The ID of the node, unique within the graph. */ diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractNode.java b/jOOQ/src/main/java/org/jooq/impl/AbstractNode.java index b34fc06c52..8ba82021f2 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractNode.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractNode.java @@ -46,6 +46,7 @@ import java.util.Map.Entry; import org.jooq.Configuration; import org.jooq.Node; import org.jooq.exception.DataDefinitionException; +import org.jooq.exception.DataMigrationVerificationException; /** * @author Lukas Eder @@ -54,7 +55,7 @@ abstract class AbstractNode> extends AbstractLazyScope impleme final N root; final String id; - final String message; + String message; @SuppressWarnings("unchecked") AbstractNode(Configuration configuration, String id, String message, N root) { diff --git a/jOOQ/src/main/java/org/jooq/impl/CommitImpl.java b/jOOQ/src/main/java/org/jooq/impl/CommitImpl.java index d054ba1321..17fa0886bf 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CommitImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/CommitImpl.java @@ -64,10 +64,12 @@ import org.jooq.DSLContext; import org.jooq.File; import org.jooq.Files; import org.jooq.Meta; +import org.jooq.Node; import org.jooq.Source; import org.jooq.Tag; import org.jooq.Version; import org.jooq.exception.DataMigrationException; +import org.jooq.exception.DataMigrationVerificationException; import org.jooq.tools.StringUtils; /** @@ -84,6 +86,9 @@ final class CommitImpl extends AbstractNode implements Commit { CommitImpl(Configuration configuration, String id, String message, Commit root, List parents, Collection delta) { super(configuration, id, message, root); + if (Node.ROOT.equals(id) && root != null) + throw new DataMigrationVerificationException("Cannot use reserved ID \"root\""); + this.ctx = configuration.dsl(); this.parents = parents; this.tags = new ArrayList<>(); @@ -363,7 +368,7 @@ final class CommitImpl extends AbstractNode implements Commit { } Map versionFiles = new HashMap<>(); - Version from = version(ctx.migrations().version("init"), id(), versionFiles, history.values()); + Version from = version(ctx.migrations().version(ROOT), id(), versionFiles, history.values()); Version to = version(from, resultCommit.id(), versionFiles, result.values()); return new FilesImpl(from, to, result.values()); } diff --git a/jOOQ/src/main/java/org/jooq/impl/CommitsImpl.java b/jOOQ/src/main/java/org/jooq/impl/CommitsImpl.java index 9cb4415a00..2eb06aa8c2 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CommitsImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/CommitsImpl.java @@ -211,6 +211,24 @@ final class CommitsImpl implements Commits { this.tags.add(new TagType().withId(tagArray[0]).withMessage(tagArray.length > 1 ? tagArray[1] : null)); } } + + @Override + public String toString() { + List strings = new ArrayList<>(); + + if (id != null) + strings.add("id: " + id); + if (version != null) + strings.add("version: " + version); + if (message != null) + strings.add("message: " + message); + if (!tags.isEmpty()) + strings.add("tags: " + tags); + if (!parentIds.isEmpty()) + strings.add("parents: " + parentIds); + + return "File: " + file + " " + strings; + } } @Override @@ -246,7 +264,7 @@ final class CommitsImpl implements Commits { List list = Stream.of(sql).map(FileData::new).collect(toList()); if (log.isDebugEnabled()) - list.forEach(f -> log.debug("Reading file", f.basename)); + list.forEach(f -> log.debug("Reading file", f)); /* * An example: @@ -320,6 +338,11 @@ final class CommitsImpl implements Commits { public final Commits load(MigrationsType migrations) { Map map = new HashMap<>(); + for (CommitType commit : migrations.getCommits()) + if (Commit.ROOT.equals(commit.getId())) + throw new DataMigrationVerificationException("Cannot define reserved commit name \"root\""); + + map.put(Commit.ROOT, new CommitType().withId(root.id()).withMessage(root.message())); for (CommitType commit : migrations.getCommits()) map.put(commit.getId(), commit); diff --git a/jOOQ/src/main/java/org/jooq/impl/HistoryImpl.java b/jOOQ/src/main/java/org/jooq/impl/HistoryImpl.java index 4cb95c4016..9aaec0b582 100644 --- a/jOOQ/src/main/java/org/jooq/impl/HistoryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/HistoryImpl.java @@ -50,12 +50,14 @@ import static org.jooq.impl.HistoryStatus.SUCCESS; import static org.jooq.impl.Tools.isEmpty; import static org.jooq.tools.StringUtils.isBlank; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import org.jooq.Commit; @@ -63,6 +65,7 @@ import org.jooq.Commits; import org.jooq.Configuration; import org.jooq.DSLContext; import org.jooq.History; +import org.jooq.HistoryVersion; import org.jooq.Schema; import org.jooq.Version; import org.jooq.conf.InterpreterSearchSchema; @@ -72,6 +75,7 @@ import org.jooq.conf.MigrationSchema; import org.jooq.conf.RenderMapping; import org.jooq.exception.DataAccessException; import org.jooq.exception.DataMigrationVerificationException; +import org.jooq.tools.JooqLogger; import org.jetbrains.annotations.Nullable; @@ -80,10 +84,12 @@ import org.jetbrains.annotations.Nullable; */ class HistoryImpl extends AbstractScope implements History { - final DSLContext ctx; - final DSLContext historyCtx; - final Commits commits; - final List versions; + private static final JooqLogger log = JooqLogger.getLogger(HistoryImpl.class); + + final DSLContext ctx; + final DSLContext historyCtx; + final Commits commits; + final List versions; HistoryImpl(Configuration configuration) { super(configuration); @@ -97,12 +103,12 @@ class HistoryImpl extends AbstractScope implements History { } @Override - public final Iterator iterator() { + public final Iterator iterator() { return unmodifiableList(versions).iterator(); } @Override - public final Version root() { + public final HistoryVersion root() { if (!isEmpty(versions)) return versions.get(0); else @@ -110,7 +116,7 @@ class HistoryImpl extends AbstractScope implements History { } @Override - public final Version current() { + public final HistoryVersion current() { if (!isEmpty(versions)) return versions.get(versions.size() - 1); else @@ -218,11 +224,15 @@ class HistoryImpl extends AbstractScope implements History { return false; } - private final List initVersions() { - List result = new ArrayList<>(); + private final List initVersions() { + List result = new ArrayList<>(); if (existsHistory()) { - result.add(commits.root().version()); + result.add(new HistoryVersionImpl( + this, + commits.root().version(), + null + )); for (HistoryRecord r : historyCtx .selectFrom(HISTORY) @@ -232,9 +242,20 @@ class HistoryImpl extends AbstractScope implements History { Commit commit = commits.get(r.getMigratedTo()); if (commit != null) - result.add(commit.version()); + result.add(new HistoryVersionImpl( + this, + commit.version(), + r.getMigratedAt().toInstant() + )); else - throw new DataMigrationVerificationException("CommitProvider didn't provide version for ID: " + r.getMigratedTo()); + throw new DataMigrationVerificationException( + """ + CommitProvider didn't provide version for ID: {id} + + This may happen if a successful migration has happened in a database, but the source + for this migration is not available. + """.replace("{id}", r.getMigratedTo()) + ); } } @@ -256,6 +277,7 @@ class HistoryImpl extends AbstractScope implements History { && historyCtx.settings().getMigrationDefaultSchema() != null) historyCtx.createSchemaIfNotExists("").execute(); + log.info("Initialising history table: " + historyCtx.map(HISTORY)); historyCtx.meta(HISTORY).ddl().executeBatch(); } } @@ -272,6 +294,31 @@ class HistoryImpl extends AbstractScope implements History { throw new DataMigrationVerificationException("No current history record found to resolve"); } + static final record HistoryVersionImpl(History history, Version version, Instant migratedAt) implements HistoryVersion { + + @Override + public int hashCode() { + return Objects.hash(version); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + HistoryVersionImpl other = (HistoryVersionImpl) obj; + return Objects.equals(version, other.version); + } + + @Override + public String toString() { + return "HistoryVersion [version=" + version + ", migratedAt=" + migratedAt + "]"; + } + } + // ------------------------------------------------------------------------- // The Object API // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java b/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java index 22a167c604..ddc5615918 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java @@ -169,7 +169,16 @@ final class MigrationImpl extends AbstractScope implements Migration { for (Schema schema : history.lookup(commit.meta().getSchemas())) if (!ctx.migratedSchemas().contains(schema)) - throw new DataMigrationVerificationException("Schema is referenced from commit, but not configured for migration: " + schema); + throw new DataMigrationVerificationException( + """ + Schema is referenced from commit, but not configured for migration: {schema} + + All schemas that are referenced from commits in a migration must be configured for + inclusion in the migration + + TODO doclink + """.replace("{schema}", schema.toString()) + ); } private final Queries revertUntrackedQueries(Set includedSchemas) { diff --git a/jOOQ/src/main/java/org/jooq/impl/MigrationsImpl.java b/jOOQ/src/main/java/org/jooq/impl/MigrationsImpl.java index 8f4ddee956..0fc3172b42 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MigrationsImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MigrationsImpl.java @@ -38,6 +38,7 @@ package org.jooq.impl; import static java.util.Collections.emptyList; +import static org.jooq.Commit.ROOT; import org.jooq.Commit; import org.jooq.Commits; @@ -75,7 +76,7 @@ final class MigrationsImpl extends AbstractScope implements Migrations { @Override public final Commits commits() { - return new CommitsImpl(configuration(), new CommitImpl(configuration(), "init", "init", null, emptyList(), emptyList())); + return new CommitsImpl(configuration(), new CommitImpl(configuration(), ROOT, ROOT, null, emptyList(), emptyList())); } @Override