[jOOQ/jOOQ#8528] Refactorings (WIP)

This commit is contained in:
Lukas Eder 2019-10-29 17:15:37 +01:00
parent 70f0fe3426
commit 8f375b6cf4
3 changed files with 237 additions and 309 deletions

View File

@ -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.
* <p>
* 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.
* <p>
* 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;
}
}

View File

@ -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.
* <p>
* 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) {}
}
}
}
}

View File

@ -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<Catalog> getCatalogs0() throws DataAccessException {
List<Catalog> 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<Schema> 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<Table<?>> getTables() {
return tables;
return Collections.unmodifiableList(tables);
}
@Override
public final List<Sequence<?>> getSequences() {
return sequences;
return Collections.unmodifiableList(sequences);
}
@Override
public final List<UDT<?>> 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<R> table) {
for (Field<?> field : table.fields())
createField(field.getName(), field.getDataType(), this, field.getComment());
for (Index index : table.getIndexes()) {
List<SortField<?>> 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<R> key : table.getKeys())
keys.add(org.jooq.impl.Internal.createUniqueKey(this, key.getName(), copiedFields(key.getFieldsArray())));
UniqueKey<R> 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<R, ?>[] copiedFields(TableField<R, ?>[] tableFields) {
@Deprecated
private final TableField<R, ?>[] fields(TableField<R, ?>[] tableFields) {
// TODO: [#9456] This auxiliary method should not be necessary
// We should be able to call TableLike.fields instead.
TableField<R, ?>[] result = new TableField[tableFields.length];
for (int i = 0; i < tableFields.length; i++)
result[i] = (TableField<R, ?>) 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<Record>) table);
static <R extends Record> DetachedTable<R> copyOf(Table<R> table, Schema owner) {
DetachedTable<R> 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<SortField<?>> 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<R> key : table.getKeys())
result.keys.add(org.jooq.impl.Internal.createUniqueKey(result, key.getName(), result.fields(key.getFieldsArray())));
UniqueKey<R> 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<R, ?> 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<Index> getIndexes() {
return indexes;
return Collections.unmodifiableList(indexes);
}
@Override
public final List<UniqueKey<R>> getKeys() {
return keys;
return Collections.unmodifiableList(keys);
}
@Override
@ -256,7 +253,7 @@ final class DetachedMeta extends AbstractMeta {
@Override
public final List<ForeignKey<R, ?>> 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());
}
}
}