[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.
This commit is contained in:
Knut Wannheden 2019-09-13 18:05:24 +02:00
parent e37d77087a
commit 7c98a5a5d8
8 changed files with 423 additions and 37 deletions

View File

@ -139,7 +139,12 @@ abstract class AbstractMeta implements Meta, Serializable {
}
}
protected abstract List<Schema> getSchemas0() throws DataAccessException;
protected List<Schema> getSchemas0() throws DataAccessException {
List<Schema> result = new ArrayList<>();
for (Catalog catalog : getCatalogs())
result.addAll(catalog.getSchemas());
return result;
}
@Override
public final List<Table<?>> getTables(String name) {
@ -176,7 +181,12 @@ abstract class AbstractMeta implements Meta, Serializable {
}
}
protected abstract List<Table<?>> getTables0() throws DataAccessException;
protected List<Table<?>> getTables0() throws DataAccessException {
List<Table<?>> result = new ArrayList<>();
for (Schema schema : getSchemas())
result.addAll(schema.getTables());
return result;
}
@Override
public final List<Sequence<?>> getSequences(String name) {
@ -213,7 +223,12 @@ abstract class AbstractMeta implements Meta, Serializable {
}
}
protected abstract List<Sequence<?>> getSequences0() throws DataAccessException;
protected List<Sequence<?>> getSequences0() throws DataAccessException {
List<Sequence<?>> result = new ArrayList<>();
for (Schema schema : getSchemas())
result.addAll(schema.getSequences());
return result;
}
@Override
public final List<UniqueKey<?>> getPrimaryKeys() throws DataAccessException {
@ -226,7 +241,13 @@ abstract class AbstractMeta implements Meta, Serializable {
cachedPrimaryKeys = new ArrayList<>(getPrimaryKeys0());
}
protected abstract List<UniqueKey<?>> getPrimaryKeys0() throws DataAccessException;
protected List<UniqueKey<?>> getPrimaryKeys0() throws DataAccessException {
List<UniqueKey<?>> result = new ArrayList<>();
for (Table<?> table : getTables())
if (table.getPrimaryKey() != null)
result.add(table.getPrimaryKey());
return result;
}
private final <T extends Named> List<T> get(Name name, Iterable<T> i, Map<Name, T> qualified, Map<Name, List<T>> unqualified) {
if (qualified.isEmpty()) {

View File

@ -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
// ------------------------------------------------------------------------

View File

@ -168,6 +168,8 @@ implements
super(name, null);
}
final Field<?>[] $primaryKey() { return primaryKey; }
// ------------------------------------------------------------------------
// XXX: QueryPart API
// ------------------------------------------------------------------------

View File

@ -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<Field<?>> $columnFields() { return columnFields; }
final List<DataType<?>> $columnTypes() { return columnTypes; }
final List<Constraint> $constraints() { return constraints; }
// ------------------------------------------------------------------------
// XXX: DSL API
// ------------------------------------------------------------------------

View File

@ -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<Name, MutableCatalog> 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<Catalog> 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<Field<?>> 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<MutableSchema> schemas = new ArrayList<>();
MutableCatalog(Name name) {
super(normalize(name));
}
@SuppressWarnings("unchecked")
@Override
public List<Schema> getSchemas() {
return Collections.unmodifiableList((List<Schema>) (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<MutableTable> 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<Table<?>> 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<Record> {
private UniqueKey<Record> 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<Record> getPrimaryKey() {
return primaryKey;
}
}
}

View File

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

View File

@ -98,39 +98,6 @@ final class DetachedMeta extends AbstractMeta {
return result;
}
@Override
protected final List<Schema> getSchemas0() throws DataAccessException {
List<Schema> result = new ArrayList<>();
for (Catalog catalog : getCatalogs())
result.addAll(catalog.getSchemas());
return result;
}
@Override
protected final List<Table<?>> getTables0() throws DataAccessException {
List<Table<?>> result = new ArrayList<>();
for (Schema schema : getSchemas())
result.addAll(schema.getTables());
return result;
}
@Override
protected final List<Sequence<?>> getSequences0() throws DataAccessException {
List<Sequence<?>> result = new ArrayList<>();
for (Schema schema : getSchemas())
result.addAll(schema.getSequences());
return result;
}
@Override
protected final List<UniqueKey<?>> getPrimaryKeys0() throws DataAccessException {
List<UniqueKey<?>> 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;

View File

@ -97,6 +97,8 @@ final class DropTableImpl extends AbstractRowCountQuery implements
this.temporary = temporary;
}
final Table<?> $table() { return table; }
// ------------------------------------------------------------------------
// XXX: DSL API
// ------------------------------------------------------------------------