Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jooq.impl.Diff Maven / Gradle / Ivy
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* Apache-2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: https://www.jooq.org/legal/licensing
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
import static java.lang.Boolean.FALSE;
import static java.util.Arrays.asList;
// ...
import static org.jooq.SQLDialect.IGNITE;
import static org.jooq.SQLDialect.MARIADB;
// ...
import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.impl.Comparators.CHECK_COMP;
import static org.jooq.impl.Comparators.FOREIGN_KEY_COMP;
import static org.jooq.impl.Comparators.INDEX_COMP;
import static org.jooq.impl.Comparators.KEY_COMP;
import static org.jooq.impl.Comparators.NAMED_COMP;
import static org.jooq.impl.ConstraintType.CHECK;
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.Tools.NO_SUPPORT_TIMESTAMP_PRECISION;
import static org.jooq.impl.Tools.allMatch;
import static org.jooq.impl.Tools.anyMatch;
import static org.jooq.tools.StringUtils.defaultIfNull;
import static org.jooq.tools.StringUtils.defaultString;
import static org.jooq.tools.StringUtils.isEmpty;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.jooq.AlterSequenceFlagsStep;
import org.jooq.Catalog;
import org.jooq.Check;
import org.jooq.Configuration;
import org.jooq.DDLExportConfiguration;
import org.jooq.DSLContext;
import org.jooq.DataType;
import org.jooq.Domain;
import org.jooq.Field;
import org.jooq.ForeignKey;
import org.jooq.Index;
import org.jooq.Key;
import org.jooq.Meta;
import org.jooq.MigrationConfiguration;
import org.jooq.Name;
import org.jooq.Named;
import org.jooq.Nullability;
import org.jooq.Queries;
import org.jooq.Query;
import org.jooq.SQLDialect;
import org.jooq.Schema;
import org.jooq.Sequence;
import org.jooq.Table;
import org.jooq.TableOptions.TableType;
import org.jooq.UniqueKey;
import org.jooq.tools.StringUtils;
/**
* A class producing a diff between two {@link Meta} objects.
*
* @author Lukas Eder
*/
final class Diff {
private static final Set NO_SUPPORT_PK_NAMES = SQLDialect.supportedBy(IGNITE, MARIADB, MYSQL);
private final MigrationConfiguration migrateConf;
private final DDLExportConfiguration exportConf;
private final DSLContext ctx;
private final Meta meta1;
private final Meta meta2;
private final DDL ddl;
Diff(Configuration configuration, MigrationConfiguration migrateConf, Meta meta1, Meta meta2) {
this.migrateConf = migrateConf;
this.exportConf = new DDLExportConfiguration().createOrReplaceView(migrateConf.createOrReplaceView());
this.ctx = configuration.dsl();
this.meta1 = meta1;
this.meta2 = meta2;
this.ddl = new DDL(ctx, exportConf);
}
final Queries queries() {
return ctx.queries(appendCatalogs(new DiffResult(), meta1.getCatalogs(), meta2.getCatalogs()).queries);
}
private final DiffResult appendCatalogs(DiffResult result, List l1, List l2) {
return append(result, l1, l2, null,
// TODO Implement this for SQL Server support.
null,
// TODO Implement this for SQL Server support.
null,
(r, c1, c2) -> appendSchemas(r, c1.getSchemas(), c2.getSchemas())
);
}
private final DiffResult appendSchemas(DiffResult result, List l1, List l2) {
return append(result, l1, l2, null,
(r, s) -> r.queries.addAll(Arrays.asList(ctx.ddl(s).queries())),
(r, s) -> {
if (s.getTables().isEmpty() && s.getSequences().isEmpty()) {
if (!StringUtils.isEmpty(s.getName()))
r.queries.add(ctx.dropSchema(s));
}
else if (migrateConf.dropSchemaCascade()) {
// TODO: Can we reuse the logic from DROP_TABLE?
for (Table> t1 : s.getTables())
for (UniqueKey> uk : t1.getKeys())
r.droppedFks.addAll(uk.getReferences());
if (!StringUtils.isEmpty(s.getName()))
r.queries.add(ctx.dropSchema(s).cascade());
}
else {
for (Table> t2 : s.getTables())
dropTable().drop(r, t2);
for (Sequence> seq : s.getSequences())
dropSequence().drop(r, seq);
if (!StringUtils.isEmpty(s.getName()))
r.queries.add(ctx.dropSchema(s));
}
},
(r, s1, s2) -> {
appendDomains(r, s1.getDomains(), s2.getDomains());
appendTables(r, s1.getTables(), s2.getTables());
appendSequences(r, s1.getSequences(), s2.getSequences());
}
);
}
private final Drop> dropSequence() {
return (r, s) -> r.queries.add(ctx.dropSequence(s));
}
private final DiffResult appendSequences(DiffResult result, List extends Sequence>> l1, List extends Sequence>> l2) {
return append(result, l1, l2, null,
(r, s) -> r.queries.add(ddl.createSequence(s)),
dropSequence(),
(r, s1, s2) -> {
AlterSequenceFlagsStep stmt = null;
AlterSequenceFlagsStep stmt0 = ctx.alterSequence(s1);
if (s2.getStartWith() != null && !s2.getStartWith().equals(s1.getStartWith()))
stmt = defaultIfNull(stmt, stmt0).startWith(s2.getStartWith());
else if (s2.getStartWith() == null && s1.getStartWith() != null)
stmt = defaultIfNull(stmt, stmt0).startWith(1);
if (s2.getIncrementBy() != null && !s2.getIncrementBy().equals(s1.getIncrementBy()))
stmt = defaultIfNull(stmt, stmt0).incrementBy(s2.getIncrementBy());
else if (s2.getIncrementBy() == null && s1.getIncrementBy() != null)
stmt = defaultIfNull(stmt, stmt0).incrementBy(1);
if (s2.getMinvalue() != null && !s2.getMinvalue().equals(s1.getMinvalue()))
stmt = defaultIfNull(stmt, stmt0).minvalue(s2.getMinvalue());
else if (s2.getMinvalue() == null && s1.getMinvalue() != null)
stmt = defaultIfNull(stmt, stmt0).noMinvalue();
if (s2.getMaxvalue() != null && !s2.getMaxvalue().equals(s1.getMaxvalue()))
stmt = defaultIfNull(stmt, stmt0).maxvalue(s2.getMaxvalue());
else if (s2.getMaxvalue() == null && s1.getMaxvalue() != null)
stmt = defaultIfNull(stmt, stmt0).noMaxvalue();
if (s2.getCache() != null && !s2.getCache().equals(s1.getCache()))
stmt = defaultIfNull(stmt, stmt0).cache(s2.getCache());
else if (s2.getCache() == null && s1.getCache() != null)
stmt = defaultIfNull(stmt, stmt0).noCache();
if (s2.getCycle() && !s1.getCycle())
stmt = defaultIfNull(stmt, stmt0).cycle();
else if (!s2.getCycle() && s1.getCycle())
stmt = defaultIfNull(stmt, stmt0).noCycle();
if (stmt != null)
r.queries.add(stmt);
}
);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private final DiffResult appendDomains(DiffResult result, List extends Domain>> l1, List extends Domain>> l2) {
return append(result, l1, l2, null,
(r, d) -> r.queries.add(ddl.createDomain(d)),
(r, d) -> r.queries.add(ctx.dropDomain(d)),
(r, d1, d2) -> {
if (!d1.getDataType().getSQLDataType().equals(d2.getDataType().getSQLDataType())) {
r.queries.addAll(Arrays.asList(ctx.dropDomain(d1), ddl.createDomain(d2)));
}
else {
if (d1.getDataType().defaulted() && !d2.getDataType().defaulted())
r.queries.add(ctx.alterDomain(d1).dropDefault());
else if (d2.getDataType().defaulted() && !d2.getDataType().defaultValue().equals(d1.getDataType().defaultValue()))
r.queries.add(ctx.alterDomain(d1).setDefault((Field) d2.getDataType().defaultValue()));
appendChecks(r, d1, d1.getChecks(), d2.getChecks());
}
}
);
}
private final Create> createTable() {
return (r, t) -> r.queries.addAll(Arrays.asList(ddl.queries(t).queries()));
}
private final Drop> dropTable() {
return (r, t) -> {
for (UniqueKey> uk : t.getKeys())
for (ForeignKey, ?> fk : uk.getReferences())
if (r.droppedFks.add(fk) && !migrateConf.dropTableCascade())
r.queries.add(ctx.alterTable(fk.getTable()).dropForeignKey(fk.constraint()));
if (t.getTableType().isView())
r.queries.add(ctx.dropView(t));
else if (t.getTableType() == TableType.TEMPORARY)
r.queries.add(ctx.dropTemporaryTable(t));
else
r.queries.add(migrateConf.dropTableCascade()
? ctx.dropTable(t).cascade()
: ctx.dropTable(t));
};
}
private final Merge> MERGE_TABLE = new Merge>() {
@Override
public void merge(DiffResult r, Table> t1, Table> t2) {
boolean v1 = t1.getTableType().isView();
boolean v2 = t2.getTableType().isView();
if (v1 && v2) {
if (!Arrays.equals(t1.fields(), t2.fields())
|| t2.getOptions().select() != null && !t2.getOptions().select().equals(t1.getOptions().select())
|| t2.getOptions().source() != null && !t2.getOptions().source().equals(t1.getOptions().source())) {
replaceView(r, t1, t2);
return;
}
}
else if (v1 != v2) {
replaceView(r, t1, t2);
return;
}
else {
// TODO: The order of dropping / adding these objects might be incorrect
// as there could be inter-dependencies.
appendColumns(r, t1, asList(t1.fields()), asList(t2.fields()));
appendPrimaryKey(r, t1, asList(t1.getPrimaryKey()), asList(t2.getPrimaryKey()));
appendUniqueKeys(r, t1, removePrimary(t1.getKeys()), removePrimary(t2.getKeys()));
appendForeignKeys(r, t1, t1.getReferences(), t2.getReferences());
appendChecks(r, t1, t1.getChecks(), t2.getChecks());
appendIndexes(r, t1, t1.getIndexes(), t2.getIndexes());
}
String c1 = defaultString(t1.getComment());
String c2 = defaultString(t2.getComment());
if (!c1.equals(c2))
if (v2)
r.queries.add(ctx.commentOnView(t2).is(c2));
else
r.queries.add(ctx.commentOnTable(t2).is(c2));
}
private void replaceView(DiffResult r, Table> v1, Table> v2) {
if (!migrateConf.createOrReplaceView())
dropTable().drop(r, v1);
createTable().create(r, v2);
}
};
private final DiffResult appendTables(DiffResult result, List extends Table>> l1, List extends Table>> l2) {
return append(result, l1, l2, null, createTable(), dropTable(), MERGE_TABLE);
}
private final List> removePrimary(List extends UniqueKey>> list) {
List> result = new ArrayList<>();
for (UniqueKey> uk : list)
if (!uk.isPrimary())
result.add(uk);
return result;
}
private final boolean isSynthetic(Field> f) {
switch (ctx.family()) {
}
return false;
}
private final boolean isSynthetic(UniqueKey> pk) {
switch (ctx.family()) {
}
return false;
}
private final DiffResult appendColumns(DiffResult result, Table> t1, List extends Field>> l1, List extends Field>> l2) {
final List> add = new ArrayList<>();
final List> drop = new ArrayList<>();
result = append(result, l1, l2, null,
(r, f) -> {
// Ignore synthetic columns
if (isSynthetic(f))
;
else if (migrateConf.alterTableAddMultiple())
add.add(f);
else
r.queries.add(ctx.alterTable(t1).add(f));
},
(r, f) -> {
// Ignore synthetic columns
if (isSynthetic(f))
;
else if (migrateConf.alterTableDropMultiple())
drop.add(f);
else
r.queries.add(ctx.alterTable(t1).drop(f));
},
new Merge>() {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void merge(DiffResult r, Field> f1, Field> f2) {
DataType> type1 = f1.getDataType();
DataType> type2 = f2.getDataType();
// TODO: Some dialects support changing nullability and types in one statement
// We should produce a single statement as well, and handle derived things
// like nullability through emulations
if (typeNameDifference(type1, type2))
r.queries.add(ctx.alterTable(t1).alter(f1).set(type2.nullability(Nullability.DEFAULT)));
if (type1.nullable() && !type2.nullable())
r.queries.add(ctx.alterTable(t1).alter(f1).setNotNull());
else if (!type1.nullable() && type2.nullable())
r.queries.add(ctx.alterTable(t1).alter(f1).dropNotNull());
Field> d1 = type1.defaultValue();
Field> d2 = type2.defaultValue();
if (type1.defaulted() && !type2.defaulted())
r.queries.add(ctx.alterTable(t1).alter(f1).dropDefault());
else if (type2.defaulted() && (!type1.defaulted() || !d2.equals(d1)))
r.queries.add(ctx.alterTable(t1).alter(f1).setDefault((Field) d2));
if ((type1.hasLength() && type2.hasLength() && (type1.lengthDefined() != type2.lengthDefined() || type1.length() != type2.length()))
|| (type1.hasPrecision() && type2.hasPrecision() && precisionDifference(type1, type2))
|| (type1.hasScale() && type2.hasScale() && (type1.scaleDefined() != type2.scaleDefined() || type1.scale() != type2.scale())))
r.queries.add(ctx.alterTable(t1).alter(f1).set(type2));
// [#9656] TODO: Change collation
// [#9656] TODO: Change character set
}
private final boolean typeNameDifference(DataType> type1, DataType> type2) {
if (type1.getTypeName().equals(type2.getTypeName()))
return false;
// [#10864] In most dialects, DECIMAL and NUMERIC are aliases and don't need to be changed into each other
else
return type1.getType() != BigDecimal.class || type2.getType() != BigDecimal.class;
}
private final boolean precisionDifference(DataType> type1, DataType> type2) {
// [#10807] Only one type has a default precision defined
boolean d1 = defaultPrecision(type1);
boolean d2 = defaultPrecision(type2);
if (d1 || d2)
return d1 != d2;
else
return type1.precision() != type2.precision();
}
private final boolean defaultPrecision(DataType> type) {
if (!type.isDateTime())
return false;
if (!type.precisionDefined())
return true;
if (NO_SUPPORT_TIMESTAMP_PRECISION.contains(ctx.dialect()))
return true;
if (FALSE.equals(ctx.settings().isMigrationIgnoreDefaultTimestampPrecisionDiffs()))
return false;
switch (ctx.family()) {
case MARIADB:
return type.precision() == 0;
// [#10807] TODO: Alternative defaults will be listed here as they are discovered
default:
return type.precision() == 6;
}
}
}
);
if (!drop.isEmpty())
result.queries.add(0, ctx.alterTable(t1).drop(drop));
if (!add.isEmpty())
result.queries.add(ctx.alterTable(t1).add(add));
return result;
}
private final DiffResult appendPrimaryKey(DiffResult result, final Table> t1, List extends UniqueKey>> pk1, List extends UniqueKey>> pk2) {
final Create> create = (r, pk) -> {
if (isSynthetic(pk))
;
else
r.queries.add(ctx.alterTable(t1).add(pk.constraint()));
};
final Drop> drop = (r, pk) -> {
if (isSynthetic(pk))
;
else if (isEmpty(pk.getName()))
r.queries.add(ctx.alterTable(t1).dropPrimaryKey());
else
r.queries.add(ctx.alterTable(t1).dropPrimaryKey(pk.constraint()));
};
return append(result, pk1, pk2, KEY_COMP,
create,
drop,
keyMerge(t1, create, drop, PRIMARY_KEY),
true
);
}
private final DiffResult appendUniqueKeys(DiffResult result, final Table> t1, List extends UniqueKey>> uk1, List extends UniqueKey>> uk2) {
final Create> create = (r, u) -> r.queries.add(ctx.alterTable(t1).add(u.constraint()));
final Drop> drop = (r, u) -> r.queries.add(ctx.alterTable(t1).dropUnique(u.constraint()));
return append(result, uk1, uk2, KEY_COMP,
create,
drop,
keyMerge(t1, create, drop, UNIQUE),
true
);
}
private final Merge keyMerge(Table> t1, Create create, Drop drop, ConstraintType type) {
return (r, k1, k2) -> {
Name n1 = k1.getUnqualifiedName();
Name n2 = k2.getUnqualifiedName();
if (n1.empty() ^ n2.empty()) {
drop.drop(r, k1);
create.create(r, k2);
return;
}
if (NAMED_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));
};
}
private final Merge keyMerge(Domain> d1, Create create, Drop drop) {
return (r, k1, k2) -> {
Name n1 = k1.getUnqualifiedName();
Name n2 = k2.getUnqualifiedName();
if (n1.empty() ^ n2.empty()) {
drop.drop(r, k1);
create.create(r, k2);
return;
}
if (NAMED_COMP.compare(k1, k2) != 0)
r.queries.add(ctx.alterDomain(d1).renameConstraint(n1).to(n2));
};
}
private final DiffResult appendForeignKeys(DiffResult result, final Table> t1, List extends ForeignKey, ?>> fk1, List extends ForeignKey, ?>> fk2) {
final Create> create = (r, fk) -> r.queries.add(ctx.alterTable(t1).add(fk.constraint()));
final Drop> drop = (r, fk) -> {
if (r.droppedFks.add(fk))
r.queries.add(ctx.alterTable(t1).dropForeignKey(fk.constraint()));
};
return append(result, fk1, fk2, FOREIGN_KEY_COMP,
create,
drop,
keyMerge(t1, create, drop, FOREIGN_KEY),
true
);
}
private final DiffResult appendChecks(DiffResult result, Table> t1, List extends Check>> c1, List extends Check>> c2) {
final Create> create = (r, c) -> r.queries.add(ctx.alterTable(t1).add(c.constraint()));
final Drop> drop = (r, c) -> r.queries.add(ctx.alterTable(t1).drop(c.constraint()));
return append(result, c1, c2, CHECK_COMP,
create,
drop,
keyMerge(t1, create, drop, CHECK),
true
);
}
private final DiffResult appendChecks(DiffResult result, Domain> d1, List extends Check>> c1, List extends Check>> c2) {
final Create> create = (r, c) -> r.queries.add(ctx.alterDomain(d1).add(c.constraint()));
final Drop> drop = (r, c) -> r.queries.add(ctx.alterDomain(d1).dropConstraint(c.constraint()));
return append(result, c1, c2, CHECK_COMP,
create,
drop,
keyMerge(d1, create, drop),
true
);
}
private final DiffResult appendIndexes(DiffResult result, Table> t1, List extends Index> l1, List extends Index> l2) {
final Create create = (r, i) -> r.queries.add((i.getUnique() ? ctx.createUniqueIndex(i) : ctx.createIndex(i)).on(t1, i.getFields()));
final Drop drop = (r, i) -> r.queries.add(ctx.dropIndex(i).on(t1));
return append(result, l1, l2, INDEX_COMP,
create,
drop,
(r, ix1, ix2) -> {
if (INDEX_COMP.compare(ix1, ix2) != 0) {
drop.drop(r, ix1);
create.create(r, ix2);
}
else if (NAMED_COMP.compare(ix1, ix2) != 0)
r.queries.add(ctx.alterTable(t1).renameIndex(ix1).to(ix2));
},
true
);
}
private final DiffResult append(
DiffResult result,
List extends N> l1,
List extends N> l2,
Comparator super N> comp,
Create create,
Drop drop,
Merge merge
) {
return append(result, l1, l2, comp, create, drop, merge, false);
}
private final DiffResult append(
DiffResult result,
List extends N> l1,
List extends N> l2,
Comparator super N> comp,
Create create,
Drop drop,
Merge merge,
boolean dropMergeCreate
) {
if (comp == null)
comp = NAMED_COMP;
N s1 = null;
N s2 = null;
Iterator extends N> i1 = sorted(l1, comp);
Iterator extends N> i2 = sorted(l2, comp);
DiffResult dropped = dropMergeCreate ? new DiffResult(new ArrayList<>(), result.droppedFks) : result;
DiffResult merged = dropMergeCreate ? new DiffResult(new ArrayList<>(), result.droppedFks) : result;
DiffResult created = dropMergeCreate ? new DiffResult(new ArrayList<>(), result.droppedFks) : result;
for (;;) {
if (s1 == null && i1.hasNext())
s1 = i1.next();
if (s2 == null && i2.hasNext())
s2 = i2.next();
if (s1 == null && s2 == null)
break;
int c = s1 == null
? 1
: s2 == null
? -1
: comp.compare(s1, s2);
if (c < 0) {
if (drop != null)
drop.drop(dropped, s1);
s1 = null;
}
else if (c > 0) {
if (create != null)
create.create(created, s2);
s2 = null;
}
else {
if (merge != null)
merge.merge(merged, s1, s2);
s1 = s2 = null;
}
}
if (dropMergeCreate) {
result.addAll(dropped);
result.addAll(merged);
result.addAll(created);
}
return result;
}
private static interface Create {
void create(DiffResult result, N named);
}
private static interface Drop {
void drop(DiffResult result, N named);
}
private static interface Merge {
void merge(DiffResult result, N named1, N named2);
}
private static final Iterator sorted(List list, Comparator super N> comp) {
List result = new ArrayList<>(list);
result.sort(comp);
return result.iterator();
}
private static final record DiffResult(List queries, Set> droppedFks) {
DiffResult() {
this(new ArrayList<>(), new HashSet<>());
}
void addAll(DiffResult other) {
queries.addAll(other.queries);
droppedFks.addAll(other.droppedFks);
}
@Override
public String toString() {
return queries.toString();
}
}
}