[jOOQ/jOOQ#18441] Diff cannot handle name swaps of constraints and other

objects
This commit is contained in:
Lukas Eder 2025-05-09 15:19:21 +02:00
parent 2efa77e6bc
commit f1d983d40d
2 changed files with 65 additions and 13 deletions

View File

@ -58,10 +58,12 @@ import static org.jooq.impl.ConstraintType.FOREIGN_KEY;
import static org.jooq.impl.ConstraintType.PRIMARY_KEY;
import static org.jooq.impl.ConstraintType.UNIQUE;
import static org.jooq.impl.CreateTableImpl.SUPPORT_NULLABLE_PRIMARY_KEY;
import static org.jooq.impl.DSL.unquotedName;
import static org.jooq.impl.Tools.NO_SUPPORT_TIMESTAMP_PRECISION;
import static org.jooq.impl.Tools.NO_SUPPORT_TIME_PRECISION;
import static org.jooq.impl.Tools.allMatch;
import static org.jooq.impl.Tools.anyMatch;
import static org.jooq.impl.Tools.autoAlias;
import static org.jooq.impl.Tools.filter;
import static org.jooq.impl.Tools.findAny;
import static org.jooq.impl.Tools.flatMap;
@ -93,6 +95,8 @@ import org.jooq.DataType;
import org.jooq.Domain;
import org.jooq.Field;
import org.jooq.ForeignKey;
import org.jooq.Function2;
import org.jooq.Function3;
import org.jooq.Index;
import org.jooq.Key;
import org.jooq.Meta;
@ -108,8 +112,6 @@ import org.jooq.Sequence;
import org.jooq.Table;
import org.jooq.TableOptions.TableType;
import org.jooq.UniqueKey;
import org.jooq.impl.QOM.DropTable;
import org.jooq.impl.QOM.PrimaryKey;
import org.jooq.tools.StringUtils;
/**
@ -428,7 +430,7 @@ final class Diff {
else {
// [#18044] [#18327] Ensure constraint / column drop / add order
DiffResult temp = new DiffResult(new ArrayList<>(), r.addedFks, r.droppedFks);
DiffResult temp = new DiffResult(new ArrayList<>(), new ArrayList<>(), r.addedFks, r.droppedFks);
appendColumns(temp, t1, t2, asList(t1.fields()), asList(t2.fields()));
appendPrimaryKey(temp, t1, asList(t1.getPrimaryKey()), asList(t2.getPrimaryKey()));
@ -723,11 +725,15 @@ final class Diff {
allowRenames = false;
}
if (allowRenames && UNQUALIFIED_COMP.compare(k1, k2) != 0)
if (allowRenames && UNQUALIFIED_COMP.compare(k1, k2) != 0) {
// [#10813] Don't rename constraints in MySQL
if (type != PRIMARY_KEY || !NO_SUPPORT_PK_NAMES.contains(ctx.dialect()))
r.queries.add(ctx.alterTable(t1).renameConstraint(n1).to(n2));
if (type != PRIMARY_KEY || !NO_SUPPORT_PK_NAMES.contains(ctx.dialect())) {
rename(r, type == CHECK ? t1.getChecks() : t1.getKeys(), n1, n2,
(_n1, _n2) -> ctx.alterTable(t1).renameConstraint(_n1).to(_n2)
);
}
}
@ -742,6 +748,28 @@ final class Diff {
};
}
private final void rename(
DiffResult r,
List<? extends Named> existing,
Name n1,
Name n2,
Function2<? super Name, ? super Name, ? extends Query> renameQuery
) {
// [#18441] Handle name swaps
if (anyMatch(existing, k -> k.getName().equals(n2.last()))) {
Name temp = unquotedName(autoAlias(ctx.configuration(), n1.append(n2)));
if (n1.qualified())
temp = n1.qualifier().append(temp);
r.queries.add(renameQuery.apply(n1, temp));
r.cleanup.add(renameQuery.apply(temp, n2));
}
else
r.queries.add(renameQuery.apply(n1, n2));
}
private final <K extends Named> Merge<K> keyMerge(Domain<?> d1, Create<K> create, Drop<K> drop) {
return (r, k1, k2) -> {
Name n1 = k1.getUnqualifiedName();
@ -813,8 +841,11 @@ final class Diff {
drop.drop(r, ix1);
create.create(r, ix2);
}
else if (UNQUALIFIED_COMP.compare(ix1, ix2) != 0)
r.queries.add(ctx.alterTable(t1).renameIndex(ix1).to(ix2));
else if (UNQUALIFIED_COMP.compare(ix1, ix2) != 0) {
rename(r, t1.getIndexes(), ix1.getUnqualifiedName(), ix2.getUnqualifiedName(),
(_i1, _i2) -> ctx.alterTable(t1).renameIndex(_i1).to(_i2)
);
}
},
true
);
@ -851,9 +882,9 @@ final class Diff {
Iterator<? extends N> i1 = sorted(l1, comp);
Iterator<? extends N> i2 = sorted(l2, comp);
DiffResult dropped = dropMergeCreate ? new DiffResult(new ArrayList<>(), result.addedFks, result.droppedFks) : result;
DiffResult merged = dropMergeCreate ? new DiffResult(new ArrayList<>(), result.addedFks, result.droppedFks) : result;
DiffResult created = dropMergeCreate ? new DiffResult(new ArrayList<>(), result.addedFks, result.droppedFks) : result;
DiffResult dropped = dropMergeCreate ? new DiffResult(new ArrayList<>(), new ArrayList<>(), result.addedFks, result.droppedFks) : result;
DiffResult merged = dropMergeCreate ? new DiffResult(new ArrayList<>(), new ArrayList<>(), result.addedFks, result.droppedFks) : result;
DiffResult created = dropMergeCreate ? new DiffResult(new ArrayList<>(), new ArrayList<>(), result.addedFks, result.droppedFks) : result;
for (;;) {
if (s1 == null && i1.hasNext())
@ -921,22 +952,24 @@ final class Diff {
private static final record DiffResult(
List<Query> queries,
List<Query> cleanup,
Set<ForeignKey<?, ?>> addedFks,
Set<ForeignKey<?, ?>> droppedFks
) {
DiffResult() {
this(new ArrayList<>(), new HashSet<>(), new HashSet<>());
this(new ArrayList<>(), new ArrayList<>(), new HashSet<>(), new HashSet<>());
}
void addAll(DiffResult other) {
queries.addAll(other.queries);
queries.addAll(other.cleanup);
addedFks.addAll(other.addedFks);
droppedFks.addAll(other.droppedFks);
}
@Override
public String toString() {
return queries.toString();
return Tools.concat(queries, cleanup).toString();
}
}

View File

@ -4020,6 +4020,17 @@ final class Tools {
static final Lazy<DSLContext> CTX = Lazy.of(() -> DSL.using(CONFIG.get()));
/**
* A possibly inefficient but stable way to generate an alias for any
* {@link QueryPart}.
* <p>
* Stability is important to profit from execution plan caching. Equal query
* parts must produce the same alias every time.
*/
static final String autoAlias(Configuration configuration, QueryPart part) {
return normaliseNameCase(configuration, autoAlias(part), false);
}
/**
* A possibly inefficient but stable way to generate an alias for any
* {@link QueryPart}.
@ -7159,6 +7170,14 @@ final class Tools {
return sb.toString();
}
/**
* Normalise a name case depending on the dialect and the setting for
* {@link ParseNameCase}.
*/
static final String normaliseNameCase(Configuration configuration, String name, boolean quoted) {
return normaliseNameCase(configuration, name, quoted, SettingsTools.parseLocale(configuration.settings()));
}
/**
* Normalise a name case depending on the dialect and the setting for
* {@link ParseNameCase}.