diff --git a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AddSnapshotMojo.java b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AddSnapshotMojo.java index 1664949e0b..a4ffea314d 100644 --- a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AddSnapshotMojo.java +++ b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AddSnapshotMojo.java @@ -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); } } diff --git a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AddUntrackedMojo.java b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AddUntrackedMojo.java index 756b9e3e56..1504963f0f 100644 --- a/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AddUntrackedMojo.java +++ b/jOOQ-migrations-maven/src/main/java/org/jooq/migrations/maven/AddUntrackedMojo.java @@ -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); } } diff --git a/jOOQ/src/main/java/org/jooq/ContentType.java b/jOOQ/src/main/java/org/jooq/ContentType.java index a3fb1d647f..d74e70f658 100644 --- a/jOOQ/src/main/java/org/jooq/ContentType.java +++ b/jOOQ/src/main/java/org/jooq/ContentType.java @@ -46,8 +46,18 @@ import org.jetbrains.annotations.ApiStatus.Experimental; * contained in {@link Commit#delta()} will be processed in the following order: *

*

+ *

+ * When undoing a migration, the order is: + *

* */ @Experimental @@ -102,6 +112,9 @@ public enum ContentType { *

* In order to restore a database, or install a new one, we don't have to go * back any further than the snapshot. + *

+ * 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, diff --git a/jOOQ/src/main/java/org/jooq/Files.java b/jOOQ/src/main/java/org/jooq/Files.java index 9cc57dd915..9d40922df2 100644 --- a/jOOQ/src/main/java/org/jooq/Files.java +++ b/jOOQ/src/main/java/org/jooq/Files.java @@ -69,6 +69,9 @@ public interface Files extends Iterable { *

* This is EXPERIMENTAL functionality and subject to change in future jOOQ * versions. + *

+ * 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 diff --git a/jOOQ/src/main/java/org/jooq/Migration.java b/jOOQ/src/main/java/org/jooq/Migration.java index 75413982e0..01b09cc990 100644 --- a/jOOQ/src/main/java/org/jooq/Migration.java +++ b/jOOQ/src/main/java/org/jooq/Migration.java @@ -68,6 +68,9 @@ public interface Migration extends Scope { /** * The last {@link ContentType#SNAPSHOT} commit that is being migrated from, * or null if there's no snapshot. + *

+ * 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(); diff --git a/jOOQ/src/main/java/org/jooq/impl/CommitImpl.java b/jOOQ/src/main/java/org/jooq/impl/CommitImpl.java index ec35e0f3c8..593af622e6 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CommitImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/CommitImpl.java @@ -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 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 files) { + Set 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 implements Commit { - - - - - - - - - - - - - - - @@ -490,74 +492,70 @@ final class CommitImpl extends AbstractNode implements Commit { for (Commit commit : commitHistory) { List 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 deletions = commitFiles.iterator(); - while (deletions.hasNext()) { - File file = deletions.next(); + Iterator 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 increments = commitFiles.iterator(); - while (increments.hasNext()) { - File file = increments.next(); + Iterator 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 implements Commit { + // Script files + Iterator 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 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 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 implements Commit { Map 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 implements Commit { } } - private static final Version version(Version from, String newId, Map files, Iterable result) { + private final Version version(Version from, String newId, Map files, Iterable result) { Version to = from; for (File file : result) { @@ -693,6 +706,10 @@ final class CommitImpl extends AbstractNode 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()); } diff --git a/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java b/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java index 9a42abd28d..168514010b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java @@ -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 diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index 9c1b522eba..0069193c61 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -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; } diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 135b34a711..c52083aa53 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -2744,6 +2744,10 @@ final class Tools { return array; } + static final Iterable iterable(Iterator iterator) { + return () -> iterator; + } + static final Iterator iterator(Iterator iterator, Function mapper) { return new Iterator() { @Override @@ -6871,6 +6875,11 @@ final class Tools { uptodate = false; return next; } + + @Override + public void remove() { + iterator.remove(); + } }; }