[jOOQ/jOOQ#9506] More work on Migrations API:
- Snapshots will be a commercial only feature - CommitImpl should reject duplicate paths - Add the ContentType.SCRIPT implementation - Files.writeString() wasn't available in JDK 8 yet
This commit is contained in:
parent
64107cd10c
commit
dfb607c1ff
@ -38,14 +38,14 @@
|
||||
package org.jooq.migrations.maven;
|
||||
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES;
|
||||
import static org.apache.maven.plugins.annotations.ResolutionScope.TEST;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.jooq.DDLExportConfiguration;
|
||||
@ -55,7 +55,6 @@ import org.jooq.HistoryVersion;
|
||||
import org.jooq.Meta;
|
||||
import org.jooq.Migration;
|
||||
import org.jooq.Queries;
|
||||
import org.jooq.exception.DataMigrationVerificationException;
|
||||
|
||||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
@ -106,6 +105,6 @@ public class AddSnapshotMojo extends AbstractMigrateMojo {
|
||||
if (getLog().isInfoEnabled())
|
||||
getLog().info("Writing snapshot to: " + file + "\n" + export);
|
||||
|
||||
Files.writeString(file.toPath(), export, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
Files.write(file.toPath(), asList(export), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,23 +37,17 @@
|
||||
*/
|
||||
package org.jooq.migrations.maven;
|
||||
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES;
|
||||
import static org.apache.maven.plugins.annotations.ResolutionScope.TEST;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.jooq.Commit;
|
||||
import org.jooq.DDLExportConfiguration;
|
||||
import org.jooq.DDLFlag;
|
||||
import org.jooq.History;
|
||||
import org.jooq.HistoryVersion;
|
||||
import org.jooq.Meta;
|
||||
import org.jooq.Migration;
|
||||
import org.jooq.Queries;
|
||||
|
||||
@ -103,7 +97,7 @@ public class AddUntrackedMojo extends AbstractMigrateMojo {
|
||||
|
||||
sb.append(untracked);
|
||||
sb.append("\n");
|
||||
Files.writeString(file.toPath(), sb.toString(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||
Files.write(file.toPath(), asList(sb.toString()), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -46,8 +46,18 @@ import org.jetbrains.annotations.ApiStatus.Experimental;
|
||||
* contained in {@link Commit#delta()} will be processed in the following order:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>{@link ContentType#INCREMENT}</li>
|
||||
* <li>{@link ContentType#SCHEMA}</li>
|
||||
* <li>{@link #SNAPSHOT} (replacing all other contents of this {@link Commit} as
|
||||
* well as previous commits, if migrating from {@link Commit#root()})</li>
|
||||
* <li>{@link #INCREMENT}</li>
|
||||
* <li>{@link #SCRIPT}</li>
|
||||
* <li>{@link #SCHEMA}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* When undoing a migration, the order is:
|
||||
* <ul>
|
||||
* <li>{@link #SCHEMA}</li>
|
||||
* <li>{@link #DECREMENT}</li>
|
||||
* </ul>
|
||||
* </ul>
|
||||
*/
|
||||
@Experimental
|
||||
@ -102,6 +112,9 @@ public enum ContentType {
|
||||
* <p>
|
||||
* In order to restore a database, or install a new one, we don't have to go
|
||||
* back any further than the snapshot.
|
||||
* <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.
|
||||
*/
|
||||
SNAPSHOT,
|
||||
|
||||
|
||||
@ -69,6 +69,9 @@ public interface Files extends Iterable<File> {
|
||||
* <p>
|
||||
* This is EXPERIMENTAL functionality and subject to change in future jOOQ
|
||||
* versions.
|
||||
* <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.
|
||||
*/
|
||||
@Nullable
|
||||
@Experimental
|
||||
|
||||
@ -68,6 +68,9 @@ public interface Migration extends Scope {
|
||||
/**
|
||||
* The last {@link ContentType#SNAPSHOT} commit that is being migrated from,
|
||||
* or <code>null</code> if there's no snapshot.
|
||||
* <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.
|
||||
*/
|
||||
@Nullable
|
||||
Commit fromSnapshot();
|
||||
|
||||
@ -40,10 +40,12 @@ 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.SCRIPT;
|
||||
import static org.jooq.ContentType.SNAPSHOT;
|
||||
import static org.jooq.impl.Tools.EMPTY_SOURCE;
|
||||
import static org.jooq.impl.Tools.anyMatch;
|
||||
import static org.jooq.impl.Tools.filter;
|
||||
import static org.jooq.impl.Tools.iterable;
|
||||
import static org.jooq.tools.StringUtils.isBlank;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
@ -74,6 +76,7 @@ import org.jooq.Source;
|
||||
import org.jooq.Tag;
|
||||
import org.jooq.Version;
|
||||
import org.jooq.exception.DataMigrationVerificationException;
|
||||
import org.jooq.impl.DefaultParseContext.IgnoreQuery;
|
||||
import org.jooq.tools.StringUtils;
|
||||
|
||||
/**
|
||||
@ -108,6 +111,20 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
|
||||
this.tags = new ArrayList<>();
|
||||
this.delta = map(delta, false);
|
||||
this.valid = valid;
|
||||
|
||||
if (delta.size() > this.delta.size()) {
|
||||
throw new DataMigrationVerificationException("Path is ambiguous within commit: " + duplicatePath(delta));
|
||||
}
|
||||
}
|
||||
|
||||
private static final String duplicatePath(Collection<? extends File> files) {
|
||||
Set<String> paths = new HashSet<>();
|
||||
|
||||
for (File file : files)
|
||||
if (!paths.add(file.path()))
|
||||
return file.path();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private CommitImpl(CommitImpl copy, boolean newValid) {
|
||||
@ -397,21 +414,6 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -490,74 +492,70 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
|
||||
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
|
||||
|
||||
|
||||
|
||||
if (isRoot && anyMatch(commitFiles, f -> f.type() == SNAPSHOT)) {
|
||||
result.clear();
|
||||
configuration().requireCommercial(() -> "Snapshots are a commercial only feature. Please upgrade to the jOOQ Professional Edition or jOOQ Enterprise Edition.");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// [#9506] TODO: Are there impacts on other maps?
|
||||
for (File f : commitFiles)
|
||||
if (f.type() == SNAPSHOT)
|
||||
result.put(f.path(), f);
|
||||
|
||||
fromSnapshotCommit = commit;
|
||||
continue commitLoop;
|
||||
}
|
||||
|
||||
// Deletions
|
||||
Iterator<File> deletions = commitFiles.iterator();
|
||||
while (deletions.hasNext()) {
|
||||
File file = deletions.next();
|
||||
Iterator<File> deletions = filter(commitFiles.iterator(), f -> f.content() == null);
|
||||
for (File file : iterable(deletions)) {
|
||||
hasDeletions |= true;
|
||||
String path = file.path();
|
||||
String tempKey = tempHistoryKeys.remove(path);
|
||||
String tempRemove = tempKey != null ? tempKey : path;
|
||||
String key = historyKeys.remove(path);
|
||||
String remove = key != null ? key : path;
|
||||
|
||||
if (file.content() == null) {
|
||||
hasDeletions |= true;
|
||||
String path = file.path();
|
||||
String tempKey = tempHistoryKeys.remove(path);
|
||||
String tempRemove = tempKey != null ? tempKey : path;
|
||||
String key = historyKeys.remove(path);
|
||||
String remove = key != null ? key : path;
|
||||
if (recordingResult && result.remove(tempRemove) == null && file.type() == INCREMENT && history.containsKey(tempRemove))
|
||||
result.put(tempRemove, file);
|
||||
|
||||
if (recordingResult && result.remove(tempRemove) == null && file.type() == INCREMENT && history.containsKey(tempRemove))
|
||||
result.put(tempRemove, file);
|
||||
else if (recordingResult && result.remove(remove) == null && file.type() == SCHEMA && history.containsKey(remove))
|
||||
result.put(remove, file);
|
||||
else
|
||||
history.remove(tempRemove);
|
||||
// TODO: Support deletions of scripts
|
||||
else if (recordingResult && result.remove(remove) == null && file.type() == SCHEMA && history.containsKey(remove))
|
||||
result.put(remove, file);
|
||||
else
|
||||
history.remove(tempRemove);
|
||||
|
||||
tempHistory.remove(path);
|
||||
deletions.remove();
|
||||
tempHistory.remove(path);
|
||||
deletions.remove();
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Increments
|
||||
Iterator<File> increments = commitFiles.iterator();
|
||||
while (increments.hasNext()) {
|
||||
File file = increments.next();
|
||||
Iterator<File> increments = filter(commitFiles.iterator(), f -> f.type() == INCREMENT);
|
||||
for (File file : iterable(increments)) {
|
||||
String path = file.path();
|
||||
File oldFile = recordingResult ? history.get(path) : history.put(path, file);
|
||||
|
||||
if (file.type() == INCREMENT) {
|
||||
String path = file.path();
|
||||
File oldFile = recordingResult ? history.get(path) : history.put(path, file);
|
||||
if (oldFile == null && !tempHistory.isEmpty() && !result.containsKey(path))
|
||||
move(tempHistory, result, tempHistoryKeys);
|
||||
|
||||
if (oldFile == null && !tempHistory.isEmpty() && !result.containsKey(path))
|
||||
move(tempHistory, result, tempHistoryKeys);
|
||||
if (recordingResult)
|
||||
result.put(path, file);
|
||||
|
||||
if (recordingResult)
|
||||
result.put(path, file);
|
||||
|
||||
increments.remove();
|
||||
increments.remove();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -572,34 +570,45 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
|
||||
|
||||
|
||||
|
||||
// Script files
|
||||
Iterator<File> scripts = filter(commitFiles.iterator(), f -> f.type() == SCRIPT);
|
||||
for (File file : iterable(scripts)) {
|
||||
String path = file.path();
|
||||
File oldFile = recordingResult ? history.get(path) : history.put(path, file);
|
||||
|
||||
if (oldFile == null && !tempHistory.isEmpty() && !result.containsKey(path))
|
||||
move(tempHistory, result, tempHistoryKeys);
|
||||
|
||||
if (recordingResult)
|
||||
result.put(path, file);
|
||||
|
||||
scripts.remove();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Schema files
|
||||
Iterator<File> schemas = commitFiles.iterator();
|
||||
while (schemas.hasNext()) {
|
||||
File file = schemas.next();
|
||||
|
||||
if (file.type() == SCHEMA) {
|
||||
String path = file.path();
|
||||
String key = commit.id() + "-" + path;
|
||||
|
||||
if (recordingResult) {
|
||||
tempHistory.put(path, file);
|
||||
tempHistoryKeys.put(path, key);
|
||||
}
|
||||
else {
|
||||
history.put(key, file);
|
||||
historyKeys.put(path, key);
|
||||
}
|
||||
|
||||
schemas.remove();
|
||||
|
||||
|
||||
|
||||
|
||||
Iterator<File> schemas = filter(commitFiles.iterator(), f -> f.type() == SCHEMA);
|
||||
for (File file : iterable(schemas)) {
|
||||
String path = file.path();
|
||||
String key = commit.id() + "-" + path;
|
||||
|
||||
if (recordingResult) {
|
||||
tempHistory.put(path, file);
|
||||
tempHistoryKeys.put(path, key);
|
||||
}
|
||||
else {
|
||||
history.put(key, file);
|
||||
historyKeys.put(path, key);
|
||||
}
|
||||
|
||||
schemas.remove();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
recordingResult |= id().equals(commit.id());
|
||||
@ -649,9 +658,13 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
|
||||
|
||||
Map<String, File> versionFiles = new HashMap<>();
|
||||
Version from = version(ctx.migrations().version(ROOT), id(), versionFiles, history.values());
|
||||
Version fromSnapshot = fromSnapshotCommit != null
|
||||
? version(from, fromSnapshotCommit.id(), versionFiles, filter(fromSnapshotCommit.delta(), f -> f.type() == SNAPSHOT))
|
||||
: null;
|
||||
Version fromSnapshot = null;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Version to = version(from, resultCommit.id(), versionFiles, result.values());
|
||||
return new MigrationHistory(
|
||||
pathHistory,
|
||||
@ -682,7 +695,7 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Version version(Version from, String newId, Map<String, File> files, Iterable<File> result) {
|
||||
private final Version version(Version from, String newId, Map<String, File> files, Iterable<File> result) {
|
||||
Version to = from;
|
||||
|
||||
for (File file : result) {
|
||||
@ -693,6 +706,10 @@ final class CommitImpl extends AbstractNode<Commit> implements Commit {
|
||||
|
||||
if (file.type() == SCHEMA)
|
||||
to = to.commit(newId, sources(apply(files, file, true).values()).toArray(EMPTY_SOURCE));
|
||||
|
||||
// [#9506] Scripts must be ignored by the interpreter
|
||||
else if (file.type() == SCRIPT)
|
||||
to = to.apply(newId, new IgnoreQuery(file.content(), ctx.configuration()));
|
||||
else
|
||||
to = to.apply(newId, file.content());
|
||||
}
|
||||
|
||||
@ -131,8 +131,13 @@ final class MigrationImpl extends AbstractScope implements Migration {
|
||||
|
||||
@Override
|
||||
public final Commit fromSnapshot() {
|
||||
Version version = from().migrateTo(to()).fromSnapshot();
|
||||
return version != null ? commits().get(version.id()) : null;
|
||||
if (!configuration().commercial())
|
||||
return null;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -15828,7 +15828,11 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
|
||||
}
|
||||
|
||||
IgnoreQuery(String sql) {
|
||||
super(CONFIG.get());
|
||||
this(sql, CONFIG.get());
|
||||
}
|
||||
|
||||
IgnoreQuery(String sql, Configuration configuration) {
|
||||
super(configuration);
|
||||
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
@ -2744,6 +2744,10 @@ final class Tools {
|
||||
return array;
|
||||
}
|
||||
|
||||
static final <T> Iterable<T> iterable(Iterator<T> iterator) {
|
||||
return () -> iterator;
|
||||
}
|
||||
|
||||
static final <T, U> Iterator<U> iterator(Iterator<? extends T> iterator, Function<? super T, ? extends U> mapper) {
|
||||
return new Iterator<U>() {
|
||||
@Override
|
||||
@ -6871,6 +6875,11 @@ final class Tools {
|
||||
uptodate = false;
|
||||
return next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
iterator.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user