[jOOQ/jOOQ#9506] More work on Migrations API:

- Don't throw exceptions in HistoryImpl::toString
- LogMojo should log history as well
- Reuse FilePattern::fileComparator also to sort IDs when loading files
- LogMojo should also include HistoryMojo output
- Make CommitType::getId optional for per-SQL-file metadata usage
This commit is contained in:
Lukas Eder 2024-11-20 17:04:13 +01:00
parent 32c51bbc3e
commit f086212738
10 changed files with 123 additions and 23 deletions

View File

@ -90,6 +90,10 @@
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>

View File

@ -48,6 +48,7 @@ import org.jooq.Migration;
import org.jooq.Version;
import org.jooq.tools.StringUtils;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Mojo;
/**
@ -65,28 +66,30 @@ 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 (getLog().isInfoEnabled())
for (HistoryVersion version : migration.dsl().migrations().history())
log(getLog(), version);
}
if (version.version().parents().size() > 1) {
getLog().info(" Merged parents: ");
static final void log(Log log, HistoryVersion version) {
log.info(" " + string(version.migratedAt()) + " - Version: " + string(version.version()));
for (Version p : version.version().parents())
getLog().info(" - " + string(p));
}
}
if (version.version().parents().size() > 1) {
log.info(" Merged parents: ");
for (Version p : version.version().parents())
log.info(" - " + string(p));
}
}
private final String string(Instant instant) {
private static 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) {
private static final String string(Version version) {
return version.id() + (!isEmpty(version.message()) ? " (" + version.message() + ")" : "");
}
}

View File

@ -37,9 +37,16 @@
*/
package org.jooq.migrations.maven;
import static java.util.stream.Collectors.toList;
import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES;
import static org.apache.maven.plugins.annotations.ResolutionScope.TEST;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.jooq.History;
import org.jooq.HistoryVersion;
import org.jooq.Migration;
import org.jooq.Query;
import org.jooq.tools.StringUtils;
@ -63,7 +70,31 @@ public class LogMojo extends AbstractMigrateMojo {
@Override
final void execute1(Migration migration) throws Exception {
if (getLog().isInfoEnabled()) {
History history = migration.dsl().migrations().history();
List<HistoryVersion> versions = StreamSupport
.stream(history.spliterator(), false)
.collect(toList());
if (versions.isEmpty()) {
getLog().info("No migration history available yet");
}
else {
getLog().info("Migration history");
for (HistoryVersion version : versions.subList(
Math.max(0, versions.size() - 5),
versions.size()
)) {
HistoryMojo.log(getLog(), version);
}
}
Query[] queries = migration.queries().queries();
getLog().info("Outstanding queries from " + migration.from() + " to " + migration.to() + ": "
+ (queries.length == 0 ? "none" : "")
);
log(getLog(), queries);
}
}

View File

@ -168,6 +168,10 @@ public final class FilePattern {
);
}
public final Comparator<File> fileComparator() {
return fileComparator(sort());
}
public static final Comparator<File> fileComparator(Sort sort) {
if (sort == null)
sort = SEMANTIC;

View File

@ -39,6 +39,7 @@ package org.jooq.impl;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
import static org.jooq.impl.Tools.filter;
import static org.jooq.impl.Tools.isEmpty;
@ -48,6 +49,7 @@ import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@ -291,7 +293,10 @@ final class CommitsImpl implements Commits {
// [#9506] TODO: Turning a directory into a MigrationsType (and various other conversion)
// could be made reusable. This is certainly very useful for testing and interop,
// e.g. also to support other formats (Flyway, Liquibase) as source
TreeMap<String, CommitType> idToCommit = new TreeMap<>();
TreeMap<String, CommitType> idToCommit = new TreeMap<>(comparing(
java.io.File::new,
pattern.fileComparator()
));
List<FileData> list = files.stream().map(s -> new FileData(pattern, s)).collect(toList());
@ -303,6 +308,7 @@ final class CommitsImpl implements Commits {
.withId(f.id)
.withAuthor(f.author)
.withMessage(f.message)
.withTags(f.tags)
);
for (FileData f : list) {
@ -334,6 +340,9 @@ final class CommitsImpl implements Commits {
);
}
if (log.isDebugEnabled())
log.debug("Loading files into: " + new MigrationsType().withCommits(idToCommit.values()));
return load(new MigrationsType().withCommits(idToCommit.values()));
}

View File

@ -339,6 +339,9 @@ class HistoryImpl extends AbstractScope implements History {
@Override
public String toString() {
return "History [" + current() + "]";
if (!isEmpty(versions))
return "History [" + current() + "]";
else
return "History []";
}
}

View File

@ -1379,11 +1379,32 @@ final class Interpreter {
}
private static final DataDefinitionException notExists(Named named) {
return notExists(named.getClass().getSimpleName(), named);
return notExists(typeName(named), named);
}
private static final DataDefinitionException alreadyExists(Named named) {
return alreadyExists(named.getClass().getSimpleName(), named);
return alreadyExists(typeName(named), named);
}
private static final String typeName(Named named) {
// TODO: Move this to the Named interface
if (named instanceof Catalog)
return "Catalog";
else if (named instanceof Schema)
return "Schema";
else if (named instanceof Table)
return "Table";
else if (named instanceof Field)
return "Field";
else if (named instanceof Sequence)
return "Sequence";
else if (named instanceof Domain)
return "Domain";
else if (named instanceof Synonym)
return "Synonym";
else
return named.getClass().getSimpleName();
}
private static final DataDefinitionException notExists(String type, Named named) {

View File

@ -212,11 +212,37 @@ final class MigrationImpl extends AbstractScope implements Migration {
private final void revertUntracked(DefaultMigrationContext ctx, MigrationListener listener, HistoryRecord currentRecord) {
if (ctx.revertUntrackedQueries.queries().length > 0)
if (!TRUE.equals(dsl().settings().isMigrationRevertUntracked()))
throw new DataMigrationVerificationException(
"Non-empty difference between actual schema and migration from schema: " + ctx.revertUntrackedQueries +
(currentRecord == null ? ("\n\nUse Settings.migrationAutoBaseline to automatically set a baseline") : "")
);
if (!TRUE.equals(dsl().settings().isMigrationRevertUntracked())) {
if (currentRecord == null) {
throw new DataMigrationVerificationException(
"""
Non-empty difference between actual schema and migration from schema: {queries}.
Possible remedies:
- Use Settings.migrationAutoBaseline to automatically set a baseline.
""".replace("{queries}", "" + ctx.revertUntrackedQueries)
);
}
else {
throw new DataMigrationVerificationException(
"""
Non-empty difference between actual schema and migration from schema: {queries}.
This can happen for two reasons:
1) The migration specification of a version that has already been installed has been modified.
2) The database schemas contain untracked objects.
Possible remedies if 1):
- Revert changes to the migration specification and move those changes to a new version.
Possible remedies if 2):
- Use Settings.migrationRevertUntracked to automatically drop unknown objects (at your own risk!)
- Manually drop or move unknown objects outside of managed schemas.
- Update migration scripts to track missing objects.
""".replace("{queries}", "" + ctx.revertUntrackedQueries)
);
}
}
else if (listener != null)
execute(ctx, listener, ctx.revertUntrackedQueries);
}

View File

@ -25,7 +25,7 @@ import org.jooq.util.jaxb.tools.XMLBuilder;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;all&gt;
* &lt;element name="parents" type="{http://www.jooq.org/xsd/jooq-migrations-3.20.0.xsd}ParentsType" minOccurs="0"/&gt;
* &lt;element name="id" type="{http://www.w3.org/2001/XMLSchema}string"/&gt;
* &lt;element name="id" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/&gt;
* &lt;element name="message" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/&gt;
* &lt;element name="author" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/&gt;
* &lt;element name="tags" type="{http://www.jooq.org/xsd/jooq-migrations-3.20.0.xsd}TagsType" minOccurs="0"/&gt;
@ -49,7 +49,6 @@ public class CommitType implements Serializable, XMLAppendable
{
private final static long serialVersionUID = 32000L;
@XmlElement(required = true)
protected String id;
protected String message;
protected String author;

View File

@ -22,7 +22,7 @@
<complexType name="CommitType">
<all>
<element name="parents" type="tns:ParentsType" minOccurs="0" maxOccurs="1" />
<element name="id" type="string" minOccurs="1" maxOccurs="1" />
<element name="id" type="string" minOccurs="0" maxOccurs="1" />
<element name="message" type="string" minOccurs="0" maxOccurs="1" />
<element name="author" type="string" minOccurs="0" maxOccurs="1" />
<element name="tags" type="tns:TagsType" minOccurs="0" maxOccurs="1" />