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

- Support ContentType.DECREMENT (WIP)
This commit is contained in:
Lukas Eder 2024-11-25 21:23:57 +01:00
parent af23fa75cb
commit c484d10165
4 changed files with 228 additions and 7 deletions

View File

@ -70,10 +70,23 @@ public enum ContentType {
* similar statements, which are applied as increments in a migration using
* {@link Meta#apply(Queries)}.
* <p>
* Within the same {@link Commit}.
* Within the same {@link Commit}, increments are sorted according to their
* {@link File#path()}.
*/
INCREMENT,
/**
* The file contains decrement information.
* <p>
* Decrements work like {@link #INCREMENT} typed files, but are applied only
* when downgrading to a previous version, decrements are sorted in reverse
* order according to their {@link File#path()}.
* <p>
* This API is part of a commercial only feature. To use this feature,
* please use the jOOQ Professional Edition or the jOOQ Enterprise Edition.
*/
DECREMENT,
/**
* The file contains a script.
* <p>

View File

@ -37,6 +37,7 @@
*/
package org.jooq.impl;
import static org.jooq.ContentType.DECREMENT;
import static org.jooq.ContentType.INCREMENT;
import static org.jooq.ContentType.SCHEMA;
import static org.jooq.ContentType.SNAPSHOT;
@ -63,16 +64,15 @@ import java.util.Set;
import org.jooq.Commit;
import org.jooq.Configuration;
import org.jooq.ContentType;
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;
@ -277,13 +277,197 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
@Override
public final Files migrateTo(Commit resultCommit) {
if (equals(resultCommit))
if (equals(root()))
return FilesImpl.empty(ctx.migrations().version(ROOT));
else
return FilesImpl.empty(version());
// TODO: Implement reverting a branch up to the common ancestor
Commit ancestor = commonAncestor(resultCommit);
// TODO: The reverse check doesn't take into account branching
if (ancestor.equals(resultCommit)) {
configuration().requireCommercial(() -> "Reverse migrations are a commercial only feature. Please upgrade to the jOOQ Professional Edition or jOOQ Enterprise Edition.");
}
return migrateTo0(resultCommit);
}
private static final record MigrationHistory(
Map<String, Map<String, File>> pathHistory,
Files result
) {
}
private final Files migrateTo0(Commit resultCommit) {
return migrateTo1(resultCommit, false).result();
}
private final MigrationHistory migrateTo1(Commit resultCommit, boolean recordPathHistory) {
Map<String, Map<String, File>> pathHistory = recordPathHistory ? new HashMap<>() : null;
// History are all the files that have been applied before this commit
Map<String, File> history = new LinkedHashMap<>();
@ -297,18 +481,20 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
Map<String, String> tempHistoryKeys = new HashMap<>();
Deque<Commit> commitHistory = new ArrayDeque<>();
history(commitHistory, new HashSet<>(), Arrays.asList(resultCommit));
boolean recordingResult = false;
boolean hasDeletions = false;
boolean isRoot = equals(root());
Commit fromSnapshotCommit = null;
history(commitHistory, new HashSet<>(), Arrays.asList(resultCommit));
commitLoop:
for (Commit commit : commitHistory) {
List<File> commitFiles = new ArrayList<>(commit.delta());
// [#9506] Migrations from root to a snapshot can be skipped
// TODO: effectively skip all intermediary steps
// TODO: Support pathHistory
if (isRoot && anyMatch(commitFiles, f -> f.type() == SNAPSHOT)) {
result.clear();
@ -343,6 +529,8 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
tempHistory.remove(path);
deletions.remove();
// TODO: Support pathHistory
}
}
@ -362,6 +550,9 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
result.put(path, file);
increments.remove();
if (pathHistory != null)
pathHistory.computeIfAbsent(path, p -> new LinkedHashMap<>()).put(commit.id(), file);
}
}
@ -384,6 +575,9 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
}
schemas.remove();
if (pathHistory != null)
pathHistory.computeIfAbsent(path, p -> new LinkedHashMap<>()).put(commit.id(), file);
}
}
@ -405,7 +599,7 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
// Altering history is not allowed
if (!StringUtils.equals(historicFile.content(), file.content()))
throw new DataMigrationException("Cannot edit increment file that has already been applied: " + file);
throw new DataMigrationVerificationException("Cannot edit increment file that has already been applied: " + file);
// History was altered, but the alteration was reverted
else
@ -438,13 +632,18 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
? version(from, fromSnapshotCommit.id(), versionFiles, filter(fromSnapshotCommit.delta(), f -> f.type() == SNAPSHOT))
: null;
Version to = version(from, resultCommit.id(), versionFiles, result.values());
return new FilesImpl(from, fromSnapshot, to, result.values());
return new MigrationHistory(
pathHistory,
new FilesImpl(from, fromSnapshot, to, result.values())
);
}
/**
* Breadth first recursion over commit graph.
*/
private static final void history(Deque<Commit> commitHistory, Set<Commit> set, List<Commit> commits) {
// TODO: When encountering a snapshot on a single path (no branches), we can abort recursion
for (Commit commit : commits)
if (set.add(commit))
commitHistory.push(commit);
@ -456,6 +655,8 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
if (!p.isEmpty()) {
List<Commit> l = new ArrayList<>(p);
Collections.reverse(l);
// TODO: Use iteration instead of depending on tail recursion optimisation.
history(commitHistory, set, l);
}
}

View File

@ -37,6 +37,8 @@
*/
package org.jooq.impl;
import static java.util.Collections.emptyList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@ -63,6 +65,10 @@ final class FilesImpl implements Files {
this.files = new ArrayList<>(f);
}
static final Files empty(Version version) {
return new FilesImpl(version, null, version, emptyList());
}
@Override
public final Iterator<File> iterator() {
return files.iterator();

View File

@ -195,6 +195,7 @@ final class VersionImpl extends AbstractNode<Version> implements Version {
if (!target.forceApply())
return meta().migrateTo(target.meta());
// TODO: Avoid recursion here, and iterate, instead
for (Parent parent : target.parents) {
result = migrateTo(parent.version, result);