From 8f375b6cf4cd5a534e14deb9c3703671884b8e40 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 29 Oct 2019 17:15:37 +0100 Subject: [PATCH] [jOOQ/jOOQ#8528] Refactorings (WIP) --- .../org/jooq/impl/DDLDatabaseInitializer.java | 224 ------------------ .../java/org/jooq/impl/DDLMetaProvider.java | 185 ++++++++++++++- .../main/java/org/jooq/impl/DetachedMeta.java | 137 +++++------ 3 files changed, 237 insertions(+), 309 deletions(-) delete mode 100644 jOOQ/src/main/java/org/jooq/impl/DDLDatabaseInitializer.java diff --git a/jOOQ/src/main/java/org/jooq/impl/DDLDatabaseInitializer.java b/jOOQ/src/main/java/org/jooq/impl/DDLDatabaseInitializer.java deleted file mode 100644 index 572a455126..0000000000 --- a/jOOQ/src/main/java/org/jooq/impl/DDLDatabaseInitializer.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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.conf.SettingsTools.renderLocale; -import static org.jooq.impl.DSL.name; - -import java.io.Reader; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.Locale; -import java.util.Properties; -import java.util.Scanner; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.jooq.DSLContext; -import org.jooq.Name; -import org.jooq.Name.Quoted; -import org.jooq.Queries; -import org.jooq.Query; -import org.jooq.Source; -import org.jooq.VisitContext; -import org.jooq.conf.RenderNameCase; -import org.jooq.conf.Settings; -import org.jooq.conf.SettingsTools; -import org.jooq.exception.DataAccessException; -import org.jooq.tools.JooqLogger; - -/** - * Utility to create an in-memory H2 database, against which a list of DDL - * scripts are executed. - *

- * Instead of directly executing the DDL scripts against the H2 database, they - * are first parsed and translated to the H2 dialect, and only then executed - * against the H2 database. - * - * @author Knut Wannheden - */ -final class DDLDatabaseInitializer { - - private static final JooqLogger log = JooqLogger.getLogger(DDLDatabaseInitializer.class); - private static final Pattern P_NAME = Pattern.compile("(?s:.*?\"([^\"]*)\".*)"); - - private Connection connection; - private DSLContext ctx; - - private DDLDatabaseInitializer(final Settings settings) { - try { - Properties info = new Properties(); - info.put("user", "sa"); - info.put("password", ""); - connection = DriverManager.getConnection("jdbc:h2:mem:jooq-extensions-" + UUID.randomUUID(), info); - ctx = DSL.using(connection, settings); - - // [#7771] [#8011] Ignore all parsed storage clauses when executing the statements - ctx.data("org.jooq.ddl.ignore-storage-clauses", true); - - // [#8910] Parse things a bit differently for use with the DDLDatabase - ctx.data("org.jooq.ddl.parse-for-ddldatabase", true); - - final RenderNameCase nameCase = settings.getRenderNameCase(); - final Locale locale = renderLocale(ctx.settings()); - if (nameCase != null && nameCase != RenderNameCase.AS_IS) { - ctx.configuration().set(new DefaultVisitListener() { - @Override - public void visitStart(VisitContext c) { - if (c.queryPart() instanceof Name) { - Name[] parts = ((Name) c.queryPart()).parts(); - boolean changed = false; - - for (int i = 0; i < parts.length; i++) { - Name replacement = parts[i]; - switch (nameCase) { - case LOWER_IF_UNQUOTED: - if (parts[i].quoted() == Quoted.QUOTED) break; - case LOWER: - replacement = DSL.quotedName(parts[i].first().toLowerCase(locale)); - break; - - case UPPER_IF_UNQUOTED: - if (parts[i].quoted() == Quoted.QUOTED) break; - case UPPER: - replacement = DSL.quotedName(parts[i].first().toUpperCase(locale)); - break; - - default: - break; - } - if (!replacement.equals(parts[i])) { - parts[i] = replacement; - changed = true; - } - } - - if (changed) - c.queryPart(DSL.name(parts)); - } - } - }); - } - } - catch (SQLException e) { - if ("08001".equals(e.getSQLState())) - throw new DataAccessException("The h2.jar was not found on the classpath, which is required for this internal feature", e); - throw new DataAccessException("Error while exporting schema", e); - } - catch (Exception e) { - throw new DataAccessException("Error while exporting schema", e); - } - } - - static Connection initializeUsing(final Settings settings, Source... scripts) { - DDLDatabaseInitializer initializer = new DDLDatabaseInitializer(settings == null ? SettingsTools.defaultSettings() : settings); - for (Source script : scripts) - initializer.loadScript(script); - return initializer.connection(); - } - - /** - * Parses and executes the script represented by {@code reader} against the - * H2 database. If the script references a schema which doesn't exist, it - * will be automatically created first. - *

- * Any parser errors will be thrown. It is however possible to delimit - * sections which cannot be parsed using special comments. - * - * @see Settings#getParseIgnoreCommentStart() - * @see Settings#getParseIgnoreCommentStop() - */ - private final void loadScript(Source source) { - 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) { - - repeat: - for (;;) { - try { - query.execute(); - log.info(query); - break repeat; - } - catch (DataAccessException e) { - - // [#7039] Auto create missing schemas. We're using the - if ("90079" /* ErrorCode.SCHEMA_NOT_FOUND_1 */.equals(e.sqlState())) { - SQLException cause = e.getCause(SQLException.class); - - if (cause != null) { - Matcher m = P_NAME.matcher(cause.getMessage()); - - if (m.find()) { - Query createSchema = ctx.createSchemaIfNotExists(name(m.group(1))); - createSchema.execute(); - log.info(createSchema); - continue repeat; - } - } - } - - throw e; - } - } - } - } - 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) {} - } - } - - private final Connection connection() { - return connection; - } - -} diff --git a/jOOQ/src/main/java/org/jooq/impl/DDLMetaProvider.java b/jOOQ/src/main/java/org/jooq/impl/DDLMetaProvider.java index d77f130385..b7ca9b2db1 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DDLMetaProvider.java +++ b/jOOQ/src/main/java/org/jooq/impl/DDLMetaProvider.java @@ -37,13 +37,35 @@ */ package org.jooq.impl; +import static org.jooq.SQLDialect.H2; +import static org.jooq.conf.SettingsTools.renderLocale; +import static org.jooq.impl.DSL.name; + +import java.io.Reader; import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Locale; +import java.util.Properties; +import java.util.Scanner; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.jooq.Configuration; +import org.jooq.DSLContext; import org.jooq.Meta; import org.jooq.MetaProvider; -import org.jooq.SQLDialect; +import org.jooq.Name; +import org.jooq.Name.Quoted; +import org.jooq.Queries; +import org.jooq.Query; import org.jooq.Source; +import org.jooq.VisitContext; +import org.jooq.conf.RenderNameCase; +import org.jooq.conf.Settings; +import org.jooq.exception.DataAccessException; +import org.jooq.tools.JooqLogger; import org.jooq.tools.jdbc.JDBCUtils; /** @@ -55,8 +77,11 @@ import org.jooq.tools.jdbc.JDBCUtils; */ final class DDLMetaProvider implements MetaProvider { - private final Configuration configuration; - private final Source[] scripts; + private static final JooqLogger log = JooqLogger.getLogger(DDLMetaProvider.class); + private static final Pattern P_NAME = Pattern.compile("(?s:.*?\"([^\"]*)\".*)"); + + private final Configuration configuration; + private final Source[] scripts; public DDLMetaProvider(Configuration configuration, Source... scripts) { this.configuration = configuration == null ? new DefaultConfiguration() : configuration; @@ -65,16 +90,154 @@ final class DDLMetaProvider implements MetaProvider { @Override public Meta provide() { - Configuration localConfiguration = configuration.derive(); - Connection connection = DDLDatabaseInitializer.initializeUsing(localConfiguration.settings(), scripts); + Connection connection = null; + try { - localConfiguration.set(connection); - localConfiguration.set(SQLDialect.H2); - MetaProvider defaultProvider = new DefaultMetaProvider(localConfiguration); - Meta meta = DetachedMeta.copyOf(defaultProvider.provide()); - return meta; - } finally { + DDLDatabaseInitializer initializer = new DDLDatabaseInitializer(configuration.settings()); + + for (Source script : scripts) + initializer.loadScript(script); + + return DetachedMeta.detach(new DefaultMetaProvider( + configuration.derive().set(initializer.connection).set(H2) + )); + } + finally { JDBCUtils.safeClose(connection); } } + + static final class DDLDatabaseInitializer { + + private Connection connection; + private DSLContext ctx; + + private DDLDatabaseInitializer(final Settings settings) { + try { + Properties info = new Properties(); + info.put("user", "sa"); + info.put("password", ""); + connection = DriverManager.getConnection("jdbc:h2:mem:jooq-extensions-" + UUID.randomUUID(), info); + ctx = DSL.using(connection, settings); + + // [#7771] [#8011] Ignore all parsed storage clauses when executing the statements + ctx.data("org.jooq.ddl.ignore-storage-clauses", true); + + // [#8910] Parse things a bit differently for use with the DDLDatabase + ctx.data("org.jooq.ddl.parse-for-ddldatabase", true); + + final RenderNameCase nameCase = settings.getRenderNameCase(); + final Locale locale = renderLocale(ctx.settings()); + if (nameCase != null && nameCase != RenderNameCase.AS_IS) { + ctx.configuration().set(new DefaultVisitListener() { + @Override + public void visitStart(VisitContext c) { + if (c.queryPart() instanceof Name) { + Name[] parts = ((Name) c.queryPart()).parts(); + boolean changed = false; + + for (int i = 0; i < parts.length; i++) { + Name replacement = parts[i]; + switch (nameCase) { + case LOWER_IF_UNQUOTED: + if (parts[i].quoted() == Quoted.QUOTED) break; + case LOWER: + replacement = DSL.quotedName(parts[i].first().toLowerCase(locale)); + break; + + case UPPER_IF_UNQUOTED: + if (parts[i].quoted() == Quoted.QUOTED) break; + case UPPER: + replacement = DSL.quotedName(parts[i].first().toUpperCase(locale)); + break; + + default: + break; + } + if (!replacement.equals(parts[i])) { + parts[i] = replacement; + changed = true; + } + } + + if (changed) + c.queryPart(DSL.name(parts)); + } + } + }); + } + } + catch (SQLException e) { + if ("08001".equals(e.getSQLState())) + throw new DataAccessException("The h2.jar was not found on the classpath, which is required for this internal feature", e); + throw new DataAccessException("Error while exporting schema", e); + } + catch (Exception e) { + throw new DataAccessException("Error while exporting schema", e); + } + } + + /** + * Parses and executes the script represented by {@code reader} against the + * H2 database. If the script references a schema which doesn't exist, it + * will be automatically created first. + *

+ * Any parser errors will be thrown. It is however possible to delimit + * sections which cannot be parsed using special comments. + * + * @see Settings#getParseIgnoreCommentStart() + * @see Settings#getParseIgnoreCommentStop() + */ + private final void loadScript(Source source) { + 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) { + + repeat: + for (;;) { + try { + query.execute(); + log.info(query); + break repeat; + } + catch (DataAccessException e) { + + // [#7039] Auto create missing schemas. We're using the + if ("90079" /* ErrorCode.SCHEMA_NOT_FOUND_1 */.equals(e.sqlState())) { + SQLException cause = e.getCause(SQLException.class); + + if (cause != null) { + Matcher m = P_NAME.matcher(cause.getMessage()); + + if (m.find()) { + Query createSchema = ctx.createSchemaIfNotExists(name(m.group(1))); + createSchema.execute(); + log.info(createSchema); + continue repeat; + } + } + } + + throw e; + } + } + } + } + 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 973f56c347..e2cdf2fc6a 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DetachedMeta.java +++ b/jOOQ/src/main/java/org/jooq/impl/DetachedMeta.java @@ -38,16 +38,17 @@ package org.jooq.impl; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.jooq.Catalog; import org.jooq.Comment; -import org.jooq.Configuration; import org.jooq.DataType; import org.jooq.Field; import org.jooq.ForeignKey; import org.jooq.Index; import org.jooq.Meta; +import org.jooq.MetaProvider; import org.jooq.Name; import org.jooq.Package; import org.jooq.Record; @@ -70,18 +71,19 @@ import org.jooq.exception.DataAccessException; final class DetachedMeta extends AbstractMeta { private static final long serialVersionUID = 5561057000510740144L; - private Meta delegate; + private Meta delegate; - private DetachedMeta(Configuration configuration) { - super(configuration); + static Meta detach(MetaProvider provider) { + return new DetachedMeta(provider.provide()); } - private final DetachedMeta copy(Meta meta) { + private DetachedMeta(Meta meta) { + super(meta.configuration()); + delegate = meta; getCatalogs(); delegate = null; resolveReferences(); - return this; } private final void resolveReferences() { @@ -89,10 +91,6 @@ final class DetachedMeta extends AbstractMeta { ((DetachedCatalog) catalog).resolveReferences(this); } - static Meta copyOf(Meta meta) { - return new DetachedMeta(meta.configuration()).copy(meta); - } - @Override protected final List getCatalogs0() throws DataAccessException { List result = new ArrayList<>(); @@ -110,24 +108,23 @@ final class DetachedMeta extends AbstractMeta { super(name, comment); } - private final DetachedCatalog copy(Catalog catalog) { - for (Schema schema : catalog.getSchemas()) - schemas.add(DetachedSchema.copyOf(schema, this)); - return this; - } - private final void resolveReferences(Meta meta) { for (Schema schema : schemas) ((DetachedSchema) schema).resolveReferences(meta); } static DetachedCatalog copyOf(Catalog catalog) { - return new DetachedCatalog(catalog.getName(), catalog.getComment()).copy(catalog); + DetachedCatalog result = new DetachedCatalog(catalog.getName(), catalog.getComment()); + + for (Schema schema : catalog.getSchemas()) + result.schemas.add(DetachedSchema.copyOf(schema, result)); + + return result; } @Override public final List getSchemas() { - return schemas; + return Collections.unmodifiableList(schemas); } } @@ -142,38 +139,37 @@ final class DetachedMeta extends AbstractMeta { super(name, owner, comment); } - private final DetachedSchema copy(Schema schema) { - for (Table table : schema.getTables()) - tables.add(DetachedTable.copyOf(table, this)); - for (Sequence sequence : schema.getSequences()) - sequences.add(DetachedSequence.copyOf(sequence, this)); - for (UDT udt : schema.getUDTs()) - udts.add(DetachedUDT.copyOf(udt, this)); - return this; - } - static DetachedSchema copyOf(Schema schema, Catalog owner) { - return new DetachedSchema(schema.getName(), owner, schema.getComment()).copy(schema); + DetachedSchema result = new DetachedSchema(schema.getName(), owner, schema.getComment()); + + for (Table table : schema.getTables()) + result.tables.add(DetachedTable.copyOf(table, result)); + for (Sequence sequence : schema.getSequences()) + result.sequences.add(DetachedSequence.copyOf(sequence, result)); + for (UDT udt : schema.getUDTs()) + result.udts.add(DetachedUDT.copyOf(udt, result)); + + return result; } - public final void resolveReferences(Meta meta) { + final void resolveReferences(Meta meta) { for (Table table : tables) ((DetachedTable) table).resolveReferences(meta); } @Override public final List> getTables() { - return tables; + return Collections.unmodifiableList(tables); } @Override public final List> getSequences() { - return sequences; + return Collections.unmodifiableList(sequences); } @Override public final List> getUDTs() { - return udts; + return Collections.unmodifiableList(udts); } } @@ -189,48 +185,49 @@ final class DetachedMeta extends AbstractMeta { super(name, owner, null, null, comment); } - private final DetachedTable copy(Table table) { - for (Field field : table.fields()) - createField(field.getName(), field.getDataType(), this, field.getComment()); - for (Index index : table.getIndexes()) { - List> indexFields = index.getFields(); - SortField[] copiedFields = new SortField[indexFields.size()]; - for (int i = 0; i < indexFields.size(); i++) { - SortField field = indexFields.get(i); - copiedFields[i] = field(field.getName()).sort(field.getOrder()); - // [#9009] TODO NULLS FIRST / NULLS LAST - } - indexes.add(org.jooq.impl.Internal.createIndex(index.getName(), this, copiedFields, index.getUnique())); - } - for (UniqueKey key : table.getKeys()) - keys.add(org.jooq.impl.Internal.createUniqueKey(this, key.getName(), copiedFields(key.getFieldsArray()))); - UniqueKey pk = table.getPrimaryKey(); - if (pk != null) - primaryKey = org.jooq.impl.Internal.createUniqueKey(this, pk.getName(), copiedFields(pk.getFieldsArray())); - references.addAll(table.getReferences()); - return this; - } - @SuppressWarnings("unchecked") - private final TableField[] copiedFields(TableField[] tableFields) { + @Deprecated + private final TableField[] fields(TableField[] tableFields) { + + // TODO: [#9456] This auxiliary method should not be necessary + // We should be able to call TableLike.fields instead. TableField[] result = new TableField[tableFields.length]; for (int i = 0; i < tableFields.length; i++) result[i] = (TableField) field(tableFields[i].getName()); return result; } - @SuppressWarnings("unchecked") - static DetachedTable copyOf(Table table, Schema owner) { - return new DetachedTable<>(table.getUnqualifiedName(), owner, DSL.comment(table.getComment())).copy((Table) table); + static DetachedTable copyOf(Table table, Schema owner) { + DetachedTable result = new DetachedTable<>(table.getUnqualifiedName(), owner, DSL.comment(table.getComment())); + + for (Field field : table.fields()) + DetachedTable.createField(field.getName(), field.getDataType(), result, field.getComment()); + for (Index index : table.getIndexes()) { + List> indexFields = index.getFields(); + SortField[] copiedFields = new SortField[indexFields.size()]; + for (int i = 0; i < indexFields.size(); i++) { + SortField field = indexFields.get(i); + copiedFields[i] = result.field(field.getName()).sort(field.getOrder()); + // [#9009] TODO NULLS FIRST / NULLS LAST + } + result.indexes.add(org.jooq.impl.Internal.createIndex(index.getName(), result, copiedFields, index.getUnique())); + } + for (UniqueKey key : table.getKeys()) + result.keys.add(org.jooq.impl.Internal.createUniqueKey(result, key.getName(), result.fields(key.getFieldsArray()))); + UniqueKey pk = table.getPrimaryKey(); + if (pk != null) + result.primaryKey = org.jooq.impl.Internal.createUniqueKey(result, pk.getName(), result.fields(pk.getFieldsArray())); + result.references.addAll(table.getReferences()); + return result; } - public final void resolveReferences(Meta meta) { + final void resolveReferences(Meta meta) { for (int i = 0; i < references.size(); i++) { ForeignKey ref = references.get(i); Name name = ref.getKey().getTable().getQualifiedName(); Table table = resolveTable(name, meta); UniqueKey pk = table == null ? null : table.getPrimaryKey(); - references.set(i, org.jooq.impl.Internal.createForeignKey(pk, this, ref.getName(), copiedFields(ref.getFieldsArray()))); + references.set(i, org.jooq.impl.Internal.createForeignKey(pk, this, ref.getName(), fields(ref.getFieldsArray()))); } } @@ -241,12 +238,12 @@ final class DetachedMeta extends AbstractMeta { @Override public final List getIndexes() { - return indexes; + return Collections.unmodifiableList(indexes); } @Override public final List> getKeys() { - return keys; + return Collections.unmodifiableList(keys); } @Override @@ -256,7 +253,7 @@ final class DetachedMeta extends AbstractMeta { @Override public final List> getReferences() { - return references; + return Collections.unmodifiableList(references); } } @@ -267,12 +264,8 @@ final class DetachedMeta extends AbstractMeta { super(name, owner, dataType); } - private final DetachedSequence copy(Sequence sequence) { - return this; - } - static DetachedSequence copyOf(Sequence sequence, Schema owner) { - return new DetachedSequence<>(sequence.getName(), owner, sequence.getDataType()).copy(sequence); + return new DetachedSequence<>(sequence.getName(), owner, sequence.getDataType()); } } @@ -283,16 +276,12 @@ final class DetachedMeta extends AbstractMeta { super(name, owner, package_, synthetic); } - private final DetachedUDT copy(UDT udt) { - return this; - } - static DetachedUDT copyOf(UDT udt, Schema owner) { Package package_ = null; - return new DetachedUDT<>(udt.getName(), owner, package_, udt.isSynthetic()).copy(udt); + return new DetachedUDT<>(udt.getName(), owner, package_, udt.isSynthetic()); } } } \ No newline at end of file