diff --git a/jOOQ/src/main/java/org/jooq/Meta.java b/jOOQ/src/main/java/org/jooq/Meta.java index a214b61fe1..2ada8c788a 100644 --- a/jOOQ/src/main/java/org/jooq/Meta.java +++ b/jOOQ/src/main/java/org/jooq/Meta.java @@ -219,6 +219,14 @@ public interface Meta extends Scope { */ Queries ddl(DDLExportConfiguration configuration) throws DataAccessException; + /** + * Generate a migration script to get from this meta data to another one. + * + * @throws DataAccessException If something went wrong fetching the meta + * objects + */ + Queries diff(Meta other) throws DataAccessException; + /** * Export to the {@link InformationSchema} format. *

diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractMeta.java b/jOOQ/src/main/java/org/jooq/impl/AbstractMeta.java index 7e03bf2af7..9142f42989 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractMeta.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractMeta.java @@ -58,7 +58,6 @@ import org.jooq.Schema; import org.jooq.Sequence; import org.jooq.Table; import org.jooq.UniqueKey; -import org.jooq.exception.DataAccessException; import org.jooq.util.xml.jaxb.InformationSchema; /** @@ -95,7 +94,7 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable } @Override - public final List getCatalogs() throws DataAccessException { + public final List getCatalogs() { initCatalogs(); return Collections.unmodifiableList(new ArrayList<>(cachedCatalogs.values())); } @@ -108,7 +107,7 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable } } - protected abstract List getCatalogs0() throws DataAccessException; + protected abstract List getCatalogs0(); @Override public final List getSchemas(String name) { @@ -127,7 +126,7 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable } @Override - public final List getSchemas() throws DataAccessException { + public final List getSchemas() { initSchemas(); return Collections.unmodifiableList(new ArrayList<>(cachedQualifiedSchemas.values())); } @@ -145,7 +144,7 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable } } - protected List getSchemas0() throws DataAccessException { + protected List getSchemas0() { List result = new ArrayList<>(); for (Catalog catalog : getCatalogs()) result.addAll(catalog.getSchemas()); @@ -169,7 +168,7 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable } @Override - public final List> getTables() throws DataAccessException { + public final List> getTables() { initTables(); return Collections.unmodifiableList(new ArrayList<>(cachedQualifiedTables.values())); } @@ -187,7 +186,7 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable } } - protected List> getTables0() throws DataAccessException { + protected List> getTables0() { List> result = new ArrayList<>(); for (Schema schema : getSchemas()) result.addAll(schema.getTables()); @@ -211,7 +210,7 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable } @Override - public final List> getSequences() throws DataAccessException { + public final List> getSequences() { initSequences(); return Collections.unmodifiableList(new ArrayList<>(cachedQualifiedSequences.values())); } @@ -229,7 +228,7 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable } } - protected List> getSequences0() throws DataAccessException { + protected List> getSequences0() { List> result = new ArrayList<>(); for (Schema schema : getSchemas()) result.addAll(schema.getSequences()); @@ -237,7 +236,7 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable } @Override - public final List> getPrimaryKeys() throws DataAccessException { + public final List> getPrimaryKeys() { initPrimaryKeys(); return Collections.unmodifiableList(cachedPrimaryKeys); } @@ -247,7 +246,7 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable cachedPrimaryKeys = new ArrayList<>(getPrimaryKeys0()); } - protected List> getPrimaryKeys0() throws DataAccessException { + protected List> getPrimaryKeys0() { List> result = new ArrayList<>(); for (Table table : getTables()) if (table.getPrimaryKey() != null) @@ -285,21 +284,26 @@ abstract class AbstractMeta extends AbstractScope implements Meta, Serializable } @Override - public final Queries ddl() throws DataAccessException { + public final Queries ddl() { return ddl(new DDLExportConfiguration()); } // [#9396] TODO Fix this. Subclasses should not need to override this to get // correct results @Override - public /* non-final */ Queries ddl(DDLExportConfiguration exportConfiguration) throws DataAccessException { + public /* non-final */ Queries ddl(DDLExportConfiguration exportConfiguration) { return new DDL(this, exportConfiguration).queries(); } + @Override + public final Queries diff(Meta other) { + return new Diff(configuration(), this, other).queries(); + } + // [#9396] TODO Fix this. Subclasses should not need to override this to get // correct results @Override - public /* non-final */ InformationSchema informationSchema() throws DataAccessException { + public /* non-final */ InformationSchema informationSchema() { return InformationSchemaExport.exportCatalogs(configuration(), getCatalogs()); } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Diff.java b/jOOQ/src/main/java/org/jooq/impl/Diff.java new file mode 100644 index 0000000000..398708a5b1 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/Diff.java @@ -0,0 +1,229 @@ +/* + * 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 + * + * http://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 + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import org.jooq.Catalog; +import org.jooq.Configuration; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.jooq.Meta; +import org.jooq.Named; +import org.jooq.Queries; +import org.jooq.Query; +import org.jooq.Schema; +import org.jooq.Table; + +/** + * A class producing a diff between two {@link Meta} objects. + * + * @author Lukas Eder + */ +final class Diff { + + private static final NamedComparator COMP = new NamedComparator(); + private final DSLContext ctx; + private final Meta meta1; + private final Meta meta2; + + Diff(Configuration configuration, Meta meta1, Meta meta2) { + this.ctx = configuration.dsl(); + this.meta1 = meta1; + this.meta2 = meta2; + } + + final Queries queries() { + return ctx.queries(appendCatalogs(new ArrayList<>(), sorted(meta1.getCatalogs()), sorted(meta2.getCatalogs()))); + } + + private final List appendCatalogs(final List queries, final Iterator i1, final Iterator i2) { + return append(queries, i1, i2, + null, + null, + new Merge() { + @Override + public void merge(List q, Catalog c1, Catalog c2) { + appendSchemas(q, sorted(c1.getSchemas()), sorted(c2.getSchemas())); + } + } + ); + } + + private final List appendSchemas(final List queries, final Iterator i1, final Iterator i2) { + // TODO Cascade semantics when creating and deleting + return append(queries, i1, i2, + new Create() { + @Override + public void create(List q, Schema s) { + q.add(ctx.createSchema(s)); + } + }, + new Drop() { + @Override + public void drop(List q, Schema s) { + q.add(ctx.dropSchema(s)); + } + }, + new Merge() { + @Override + public void merge(List q, Schema s1, Schema s2) { + appendTables(q, sorted(s1.getTables()), sorted(s2.getTables())); + } + } + ); + } + + private final List appendTables(final List queries, final Iterator> i1, final Iterator> i2) { + // TODO Cascade semantics when creating and deleting + return append(queries, i1, i2, + new Create>() { + @Override + public void create(List q, Table t) { + q.addAll(Arrays.asList(ctx.ddl(t).queries())); + } + }, + new Drop>() { + @Override + public void drop(List q, Table t) { + q.add(ctx.dropTable(t)); + } + }, + new Merge>() { + @Override + public void merge(List q, Table t1, Table t2) { + appendColumns(queries, t1, t2, sorted(t1.fields()), sorted(t2.fields())); + } + } + ); + } + + private final List appendColumns(final List queries, final Table t1, final Table t2, final Iterator> i1, final Iterator> i2) { + return append(queries, i1, i2, + (q, f) -> q.add(ctx.alterTable(t1).add(f)), + (q, f) -> q.add(ctx.alterTable(t1).drop(f)), + null + ); + } + + private final List append( + List queries, + Iterator i1, + Iterator i2, + Create create, + Drop drop, + Merge merge + ) { + N s1 = null; + N s2 = null; + + for (;;) { + if (s1 == null && i1.hasNext()) + s1 = i1.next(); + + if (s2 == null && i2.hasNext()) + s2 = i2.next(); + + if (s1 == null && s2 == null) + break; + + int comp = s1 == null + ? 1 + : s2 == null + ? -1 + : s1.getQualifiedName().compareTo(s2.getQualifiedName()); + + if (comp < 0) { + if (drop != null) + drop.drop(queries, s1); + + s1 = null; + } + else if (comp > 0) { + if (create != null) + create.create(queries, s2); + + s2 = null; + } + else { + if (merge != null) + merge.merge(queries, s1, s2); + + s1 = s2 = null; + } + } + + return queries; + } + + private static interface Create { + void create(List queries, N named); + } + + private static interface Drop { + void drop(List queries, N named); + } + + private static interface Merge { + void merge(List queries, N named1, N named2); + } + + private static final Iterator sorted(N... array) { + List result = Arrays.asList(array); + Collections.sort(result, COMP); + return result.iterator(); + } + + private static final Iterator sorted(List list) { + List result = new ArrayList<>(list); + Collections.sort(result, COMP); + return result.iterator(); + } + + private static final class NamedComparator implements Comparator { + @Override + public int compare(Named o1, Named o2) { + return o1.getQualifiedName().compareTo(o2.getQualifiedName()); + } + } +}