From 7c98a5a5d8b7a45d2f48822b86ba05238752f13f Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 13 Sep 2019 18:05:24 +0200 Subject: [PATCH] [jOOQ/jOOQ#8528] Initial work on DDL simulation Adds a new `DDLInterpreterMetaProvider` (might eventually replace `DDLMetaProvider`) which interprets DDL scripts by first parsing them using jOOQ's `Parser`. To extract information the implementation classes like `CreateTableImpl` have new package private accessors. To avoid name conflicts with statically imported methods (e.g. `DSL#table()`), the accessor methods have a `$`-prefix. This is just a temporary solution until the object query model API is implemented, at which point these methods will be deleted again. The `DDLInterpreter` for now just implements very basic `CREATE TABLE`, `ALTER TABLE`, and `DROP TABLE` support. The implementation will be completed incrementally. --- .../main/java/org/jooq/impl/AbstractMeta.java | 29 +- .../java/org/jooq/impl/AlterTableImpl.java | 4 + .../java/org/jooq/impl/ConstraintImpl.java | 2 + .../java/org/jooq/impl/CreateTableImpl.java | 6 + .../java/org/jooq/impl/DDLInterpreter.java | 273 ++++++++++++++++++ .../jooq/impl/DDLInterpreterMetaProvider.java | 111 +++++++ .../main/java/org/jooq/impl/DetachedMeta.java | 33 --- .../java/org/jooq/impl/DropTableImpl.java | 2 + 8 files changed, 423 insertions(+), 37 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/DDLInterpreter.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/DDLInterpreterMetaProvider.java diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractMeta.java b/jOOQ/src/main/java/org/jooq/impl/AbstractMeta.java index ebcf60993f..39e2ec8d07 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractMeta.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractMeta.java @@ -139,7 +139,12 @@ abstract class AbstractMeta implements Meta, Serializable { } } - protected abstract List getSchemas0() throws DataAccessException; + protected List getSchemas0() throws DataAccessException { + List result = new ArrayList<>(); + for (Catalog catalog : getCatalogs()) + result.addAll(catalog.getSchemas()); + return result; + } @Override public final List> getTables(String name) { @@ -176,7 +181,12 @@ abstract class AbstractMeta implements Meta, Serializable { } } - protected abstract List> getTables0() throws DataAccessException; + protected List> getTables0() throws DataAccessException { + List> result = new ArrayList<>(); + for (Schema schema : getSchemas()) + result.addAll(schema.getTables()); + return result; + } @Override public final List> getSequences(String name) { @@ -213,7 +223,12 @@ abstract class AbstractMeta implements Meta, Serializable { } } - protected abstract List> getSequences0() throws DataAccessException; + protected List> getSequences0() throws DataAccessException { + List> result = new ArrayList<>(); + for (Schema schema : getSchemas()) + result.addAll(schema.getSequences()); + return result; + } @Override public final List> getPrimaryKeys() throws DataAccessException { @@ -226,7 +241,13 @@ abstract class AbstractMeta implements Meta, Serializable { cachedPrimaryKeys = new ArrayList<>(getPrimaryKeys0()); } - protected abstract List> getPrimaryKeys0() throws DataAccessException; + protected List> getPrimaryKeys0() throws DataAccessException { + List> result = new ArrayList<>(); + for (Table table : getTables()) + if (table.getPrimaryKey() != null) + result.add(table.getPrimaryKey()); + return result; + } private final List get(Name name, Iterable i, Map qualified, Map> unqualified) { if (qualified.isEmpty()) { diff --git a/jOOQ/src/main/java/org/jooq/impl/AlterTableImpl.java b/jOOQ/src/main/java/org/jooq/impl/AlterTableImpl.java index e6960aee90..1b6a12e769 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AlterTableImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/AlterTableImpl.java @@ -261,6 +261,10 @@ final class AlterTableImpl extends AbstractRowCountQuery implements this.ifExists = ifExists; } + final Table $table() { return table; } + final Field $addColumn() { return addColumn; } + final DataType $addColumnType() { return addColumnType; } + // ------------------------------------------------------------------------ // XXX: DSL API // ------------------------------------------------------------------------ diff --git a/jOOQ/src/main/java/org/jooq/impl/ConstraintImpl.java b/jOOQ/src/main/java/org/jooq/impl/ConstraintImpl.java index 25ad99903c..451ea1e410 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ConstraintImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ConstraintImpl.java @@ -168,6 +168,8 @@ implements super(name, null); } + final Field[] $primaryKey() { return primaryKey; } + // ------------------------------------------------------------------------ // XXX: QueryPart API // ------------------------------------------------------------------------ diff --git a/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java b/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java index f10ee95a34..fb2794b060 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java @@ -182,6 +182,12 @@ final class CreateTableImpl extends AbstractRowCountQuery implements this.indexes = new ArrayList<>(); } + final Table $table() { return table; } + final Select $select() { return select; } + final List> $columnFields() { return columnFields; } + final List> $columnTypes() { return columnTypes; } + final List $constraints() { return constraints; } + // ------------------------------------------------------------------------ // XXX: DSL API // ------------------------------------------------------------------------ diff --git a/jOOQ/src/main/java/org/jooq/impl/DDLInterpreter.java b/jOOQ/src/main/java/org/jooq/impl/DDLInterpreter.java new file mode 100644 index 0000000000..769548feed --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/DDLInterpreter.java @@ -0,0 +1,273 @@ +/* + * 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 static org.jooq.impl.DSL.unquotedName; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.jooq.Catalog; +import org.jooq.Constraint; +import org.jooq.DataType; +import org.jooq.Field; +import org.jooq.Meta; +import org.jooq.Name; +import org.jooq.Name.Quoted; +import org.jooq.Query; +import org.jooq.Record; +import org.jooq.Schema; +import org.jooq.Table; +import org.jooq.TableField; +import org.jooq.UniqueKey; +import org.jooq.exception.DataAccessException; + +@SuppressWarnings("serial") +final class DDLInterpreter { + + private final Map catalogs = new LinkedHashMap<>(); + + private final MutableCatalog defaultCatalog; + private final MutableSchema defaultSchema; + private MutableSchema currentSchema; + + DDLInterpreter() { + defaultCatalog = new MutableCatalog(null); + catalogs.put(defaultCatalog.getUnqualifiedName(), defaultCatalog); + defaultSchema = new MutableSchema(null, defaultCatalog); + currentSchema = defaultSchema; + } + + Meta meta() { + return new AbstractMeta() { + @Override + protected List getCatalogs0() throws DataAccessException { + return new ArrayList<>(catalogs.values()); + } + }; + } + + final void accept(Query query) { + if (query instanceof CreateTableImpl) + accept0((CreateTableImpl) query); + else if (query instanceof AlterTableImpl) + accept0((AlterTableImpl) query); + else if (query instanceof DropTableImpl) + accept0((DropTableImpl) query); + else + throw new UnsupportedOperationException(query.getSQL()); + } + + private final void accept0(CreateTableImpl query) { + Table table = query.$table(); + MutableSchema schema = getSchema(table.getSchema(), true); + + // TODO ifNotExists + MutableTable t = new MutableTable(table.getUnqualifiedName(), schema); + List> columns = query.$columnFields(); + if (!columns.isEmpty()) + for (int i = 0; i < columns.size(); i++) { + Field column = columns.get(i); + t.addColumn(column.getUnqualifiedName(), query.$columnTypes().get(i)); + } + else if (query.$select() != null) + for (Field column : query.$select().fields()) + t.addColumn(column.getUnqualifiedName(), column.getDataType()); + + for (Constraint constraint : query.$constraints()) + if (constraint instanceof ConstraintImpl) { + ConstraintImpl impl = (ConstraintImpl) constraint; + t.primaryKey(impl.$primaryKey()); + } + else + // XXX log warning? + ; + } + + private final void accept0(AlterTableImpl query) { + Table table = query.$table(); + MutableSchema schema = getSchema(table.getSchema(), false); + + Field addColumn = query.$addColumn(); + if (addColumn != null) { + MutableTable existing = schema.getTable(table.getUnqualifiedName()); + existing.addColumn(addColumn.getUnqualifiedName(), query.$addColumnType()); + } + } + + private final void accept0(DropTableImpl query) { + Table table = query.$table(); + MutableSchema schema = getSchema(table.getSchema(), false); + + // TODO schema == null + MutableTable oldTable = schema.dropTable(table.getUnqualifiedName()); + // TODO oldTable == null + } + + private final MutableSchema getSchema(Schema input, boolean create) { + if (input == null) + return currentSchema; + + MutableCatalog catalog = defaultCatalog; + if (input.getCatalog() != null) { + Name catalogName = input.getCatalog().getUnqualifiedName(); + if ((catalog = catalogs.get(catalogName)) == null && create) + catalogs.put(catalogName, catalog = new MutableCatalog(catalogName)); + } + + if (catalog == null) + return null; + + MutableSchema schema = defaultSchema; + Name schemaName = input.getUnqualifiedName(); + if ((schema = catalog.getSchema(schemaName)) == null && create) + // TODO createSchemaIfNotExists should probably be configurable + schema = new MutableSchema(schemaName, catalog); + + return schema; + } + + private static Name normalize(Name name) { + if (name == null) + return null; + if (name instanceof UnqualifiedName) { + if (name.quoted() == Quoted.QUOTED) + return name; + String lowerCase = name.first().toLowerCase(); + return name.first() == lowerCase ? name : unquotedName(lowerCase); + } + + Name[] parts = name.parts(); + for (int i = 0; i < parts.length; i++) + parts[i] = normalize(parts[i]); + return DSL.name(parts); + } + + private static class MutableCatalog extends CatalogImpl { + private List schemas = new ArrayList<>(); + + MutableCatalog(Name name) { + super(normalize(name)); + } + + @SuppressWarnings("unchecked") + @Override + public List getSchemas() { + return Collections.unmodifiableList((List) (List) schemas); + } + + MutableSchema getSchema(Name name) { + for (MutableSchema schema : schemas) + if (schema.getUnqualifiedName().equals(name)) + return schema; + return null; + } + } + + private static class MutableSchema extends SchemaImpl { + private final MutableCatalog catalog; + private final List tables = new ArrayList<>(); + + MutableSchema(Name name, MutableCatalog catalog) { + super(normalize(name), null); + this.catalog = catalog; + catalog.schemas.add(this); + } + + @Override + public Catalog getCatalog() { + return catalog; + } + + @Override + public List> getTables() { + return Collections.unmodifiableList(tables); + } + + public MutableTable getTable(Name name) { + for (MutableTable table : tables) + if (table.getUnqualifiedName().equals(name)) + return table; + return null; + } + + public MutableTable dropTable(Name name) { + name = normalize(name); + for (int i = 0; i < tables.size(); i++) + if (tables.get(i).getUnqualifiedName().equals(name)) + return tables.remove(i); + return null; + } + + } + + private static class MutableTable extends TableImpl { + + private UniqueKey primaryKey; + + public MutableTable(Name name, MutableSchema schema) { + super(normalize(name), schema); + schema.tables.add(this); + } + + public void addColumn(Name name, DataType dataType) { + createField(normalize(name), dataType); + } + + public void primaryKey(Field[] primaryKeyFields) { + if (primaryKeyFields != null) + this.primaryKey = new UniqueKeyImpl(this, copiedFields(primaryKeyFields)); + } + + private final TableField[] copiedFields(Field[] input) { + TableField[] result = new TableField[input.length]; + for (int i = 0; i < input.length; i++) + result[i] = (TableField) field(normalize(input[i].getUnqualifiedName())); + return result; + } + + @Override + public UniqueKey getPrimaryKey() { + return primaryKey; + } + } + +} diff --git a/jOOQ/src/main/java/org/jooq/impl/DDLInterpreterMetaProvider.java b/jOOQ/src/main/java/org/jooq/impl/DDLInterpreterMetaProvider.java new file mode 100644 index 0000000000..85cce0a0f0 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/DDLInterpreterMetaProvider.java @@ -0,0 +1,111 @@ +/* + * 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.io.Reader; +import java.util.Scanner; + +import org.jooq.Configuration; +import org.jooq.DSLContext; +import org.jooq.Meta; +import org.jooq.MetaProvider; +import org.jooq.Queries; +import org.jooq.Query; +import org.jooq.Source; +import org.jooq.tools.JooqLogger; + +/** + * {@link MetaProvider} implementation which can {@link MetaProvider#provide() + * provide} a {@link Meta} implementation based on a set of DDL scripts as the + * input. + *

+ * In contrast to {@link DDLMetaProvider} this implementation interprets the DDL + * scripts. + * + * @author Knut Wannheden + */ +final class DDLInterpreterMetaProvider implements MetaProvider { + + private static final JooqLogger log = JooqLogger.getLogger(DDLInterpreterMetaProvider.class); + + // FIXME this exception is obviously a hack we need to remove again... + private final Configuration configuration; + private final Source[] scripts; + + public DDLInterpreterMetaProvider(Configuration configuration, Source... scripts) { + this.configuration = configuration == null ? new DefaultConfiguration() : configuration; + this.scripts = scripts; + } + + @Override + public Meta provide() { + final DDLInterpreter interpreter = new DDLInterpreter(); + Configuration localConfiguration = configuration.derive(); + DSLContext ctx = DSL.using(localConfiguration); + for (Source script : scripts) + loadScript(ctx, script, interpreter); + + return interpreter.meta(); + } + + private final void loadScript(DSLContext ctx, Source source, DDLInterpreter interpreter) { + Reader reader = source.reader(); + try { + Scanner s = new Scanner(reader).useDelimiter("\\A"); + Queries queries = ctx.parser().parse(s.hasNext() ? s.next() : ""); + + for (Query query : queries) { + interpreter.accept(query); + log.info(query); + } + } + catch (ParserException e) { + log.error("An exception occurred while parsing a DDL script: " + e.getMessage() + + ". Please report this error to https://github.com/jOOQ/jOOQ/issues/new", e); + throw e; + } + finally { + if (reader != null) + try { + reader.close(); + } + catch (Exception ignore) {} + } + } + +} diff --git a/jOOQ/src/main/java/org/jooq/impl/DetachedMeta.java b/jOOQ/src/main/java/org/jooq/impl/DetachedMeta.java index 372afc08b7..b5aa4f13e9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DetachedMeta.java +++ b/jOOQ/src/main/java/org/jooq/impl/DetachedMeta.java @@ -98,39 +98,6 @@ final class DetachedMeta extends AbstractMeta { return result; } - @Override - protected final List getSchemas0() throws DataAccessException { - List result = new ArrayList<>(); - for (Catalog catalog : getCatalogs()) - result.addAll(catalog.getSchemas()); - return result; - } - - @Override - protected final List> getTables0() throws DataAccessException { - List> result = new ArrayList<>(); - for (Schema schema : getSchemas()) - result.addAll(schema.getTables()); - return result; - } - - @Override - protected final List> getSequences0() throws DataAccessException { - List> result = new ArrayList<>(); - for (Schema schema : getSchemas()) - result.addAll(schema.getSequences()); - return result; - } - - @Override - protected final List> getPrimaryKeys0() throws DataAccessException { - List> result = new ArrayList<>(); - for (Table table : getTables()) - if (table.getPrimaryKey() != null) - result.add(table.getPrimaryKey()); - return result; - } - private static class DetachedCatalog extends CatalogImpl { private static final long serialVersionUID = 7979890261252183486L; diff --git a/jOOQ/src/main/java/org/jooq/impl/DropTableImpl.java b/jOOQ/src/main/java/org/jooq/impl/DropTableImpl.java index eb11cf45e2..d846b79d5c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DropTableImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/DropTableImpl.java @@ -97,6 +97,8 @@ final class DropTableImpl extends AbstractRowCountQuery implements this.temporary = temporary; } + final Table $table() { return table; } + // ------------------------------------------------------------------------ // XXX: DSL API // ------------------------------------------------------------------------