[jOOQ/jOOQ#9677] Add Settings.interpreterSearchPath

This commit is contained in:
Lukas Eder 2019-12-16 13:46:58 +01:00
parent 37b11143f5
commit c33cd0bd8d
10 changed files with 461 additions and 31 deletions

View File

@ -37,7 +37,8 @@
*/
package org.jooq;
import org.jooq.exception.DataDefinitionException;
import org.jooq.exception.DataMigrationException;
import org.jooq.exception.DataMigrationValidationException;
/**
* An executable migration between two {@link Version} instances.
@ -61,11 +62,26 @@ public interface Migration extends Scope {
*/
Queries queries();
/**
* Validate a migration.
*
* @throws DataMigrationValidationException When something went wrong during
* the validation of the migration.
*/
void validate() throws DataMigrationValidationException;
/**
* Apply the migration.
*
* @throws DataDefinitionException When something went wrong during the
* @throws DataMigrationException When something went wrong during the
* application of the migration.
*/
MigrationResult execute() throws DataDefinitionException;
MigrationResult execute() throws DataMigrationException;
/**
* The result of a {@link Migration} execution.
*/
public interface MigrationResult {
}
}

View File

@ -0,0 +1,141 @@
package org.jooq.conf;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import org.jooq.util.jaxb.tools.XMLAppendable;
import org.jooq.util.jaxb.tools.XMLBuilder;
/**
* A schema that is on the search path.
*
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "InterpreterSearchSchema", propOrder = {
})
@SuppressWarnings({
"all"
})
public class InterpreterSearchSchema
extends SettingsBase
implements Serializable, Cloneable, XMLAppendable
{
private final static long serialVersionUID = 31200L;
protected String catalog;
@XmlElement(required = true)
protected String schema;
/**
* The catalog qualifier of the schema, if applicable.
*
*/
public String getCatalog() {
return catalog;
}
/**
* The catalog qualifier of the schema, if applicable.
*
*/
public void setCatalog(String value) {
this.catalog = value;
}
/**
* The schema qualifier whose elements can be found from the search path.
*
*/
public String getSchema() {
return schema;
}
/**
* The schema qualifier whose elements can be found from the search path.
*
*/
public void setSchema(String value) {
this.schema = value;
}
/**
* The catalog qualifier of the schema, if applicable.
*
*/
public InterpreterSearchSchema withCatalog(String value) {
setCatalog(value);
return this;
}
/**
* The schema qualifier whose elements can be found from the search path.
*
*/
public InterpreterSearchSchema withSchema(String value) {
setSchema(value);
return this;
}
@Override
public final void appendTo(XMLBuilder builder) {
builder.append("catalog", catalog);
builder.append("schema", schema);
}
@Override
public String toString() {
XMLBuilder builder = XMLBuilder.nonFormatting();
appendTo(builder);
return builder.toString();
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass()!= that.getClass()) {
return false;
}
InterpreterSearchSchema other = ((InterpreterSearchSchema) that);
if (catalog == null) {
if (other.catalog!= null) {
return false;
}
} else {
if (!catalog.equals(other.catalog)) {
return false;
}
}
if (schema == null) {
if (other.schema!= null) {
return false;
}
} else {
if (!schema.equals(other.schema)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = ((prime*result)+((catalog == null)? 0 :catalog.hashCode()));
result = ((prime*result)+((schema == null)? 0 :schema.hashCode()));
return result;
}
}

View File

@ -49,6 +49,14 @@ public class ObjectFactory {
return new ParseSearchSchema();
}
/**
* Create an instance of {@link InterpreterSearchSchema }
*
*/
public InterpreterSearchSchema createInterpreterSearchSchema() {
return new InterpreterSearchSchema();
}
/**
* Create an instance of {@link RenderMapping }
*

View File

@ -196,6 +196,8 @@ public class Settings
@XmlElement(type = String.class)
@XmlJavaTypeAdapter(LocaleAdapter.class)
protected Locale interpreterLocale;
@XmlElement(defaultValue = "true")
protected Boolean migrationAutoValidation = true;
@XmlElement(type = String.class)
@XmlJavaTypeAdapter(LocaleAdapter.class)
protected Locale locale;
@ -223,6 +225,9 @@ public class Settings
protected String parseIgnoreCommentStart = "[jooq ignore start]";
@XmlElement(defaultValue = "[jooq ignore stop]")
protected String parseIgnoreCommentStop = "[jooq ignore stop]";
@XmlElementWrapper(name = "interpreterSearchPath")
@XmlElement(name = "schema")
protected List<InterpreterSearchSchema> interpreterSearchPath;
@XmlElementWrapper(name = "parseSearchPath")
@XmlElement(name = "schema")
protected List<ParseSearchSchema> parseSearchPath;
@ -1654,6 +1659,30 @@ public class Settings
this.interpreterLocale = value;
}
/**
* Whether a migration automatically runs a validation first.
*
* @return
* possible object is
* {@link Boolean }
*
*/
public Boolean isMigrationAutoValidation() {
return migrationAutoValidation;
}
/**
* Sets the value of the migrationAutoValidation property.
*
* @param value
* allowed object is
* {@link Boolean }
*
*/
public void setMigrationAutoValidation(Boolean value) {
this.migrationAutoValidation = value;
}
/**
* The Locale to be used with any locale dependent logic if there is not a more specific locale available. More specific locales include e.g. {@link #getRenderLocale()}, {@link #getParseLocale()}, or {@link #getInterpreterLocale()}.
*
@ -1822,6 +1851,17 @@ public class Settings
this.parseIgnoreCommentStop = value;
}
public List<InterpreterSearchSchema> getInterpreterSearchPath() {
if (interpreterSearchPath == null) {
interpreterSearchPath = new ArrayList<InterpreterSearchSchema>();
}
return interpreterSearchPath;
}
public void setInterpreterSearchPath(List<InterpreterSearchSchema> interpreterSearchPath) {
this.interpreterSearchPath = interpreterSearchPath;
}
public List<ParseSearchSchema> getParseSearchPath() {
if (parseSearchPath == null) {
parseSearchPath = new ArrayList<ParseSearchSchema>();
@ -2367,6 +2407,11 @@ public class Settings
return this;
}
public Settings withMigrationAutoValidation(Boolean value) {
setMigrationAutoValidation(value);
return this;
}
/**
* The Locale to be used with any locale dependent logic if there is not a more specific locale available. More specific locales include e.g. {@link #getRenderLocale()}, {@link #getParseLocale()}, or {@link #getInterpreterLocale()}.
*
@ -2453,6 +2498,27 @@ public class Settings
return this;
}
public Settings withInterpreterSearchPath(InterpreterSearchSchema... values) {
if (values!= null) {
for (InterpreterSearchSchema value: values) {
getInterpreterSearchPath().add(value);
}
}
return this;
}
public Settings withInterpreterSearchPath(Collection<InterpreterSearchSchema> values) {
if (values!= null) {
getInterpreterSearchPath().addAll(values);
}
return this;
}
public Settings withInterpreterSearchPath(List<InterpreterSearchSchema> interpreterSearchPath) {
setInterpreterSearchPath(interpreterSearchPath);
return this;
}
public Settings withParseSearchPath(ParseSearchSchema... values) {
if (values!= null) {
for (ParseSearchSchema value: values) {
@ -2542,6 +2608,7 @@ public class Settings
builder.append("interpreterDialect", interpreterDialect);
builder.append("interpreterNameLookupCaseSensitivity", interpreterNameLookupCaseSensitivity);
builder.append("interpreterLocale", interpreterLocale);
builder.append("migrationAutoValidation", migrationAutoValidation);
builder.append("locale", locale);
builder.append("parseDialect", parseDialect);
builder.append("parseLocale", parseLocale);
@ -2552,6 +2619,7 @@ public class Settings
builder.append("parseIgnoreComments", parseIgnoreComments);
builder.append("parseIgnoreCommentStart", parseIgnoreCommentStart);
builder.append("parseIgnoreCommentStop", parseIgnoreCommentStop);
builder.append("interpreterSearchPath", "schema", interpreterSearchPath);
builder.append("parseSearchPath", "schema", parseSearchPath);
}
@ -3168,6 +3236,15 @@ public class Settings
return false;
}
}
if (migrationAutoValidation == null) {
if (other.migrationAutoValidation!= null) {
return false;
}
} else {
if (!migrationAutoValidation.equals(other.migrationAutoValidation)) {
return false;
}
}
if (locale == null) {
if (other.locale!= null) {
return false;
@ -3258,6 +3335,15 @@ public class Settings
return false;
}
}
if (interpreterSearchPath == null) {
if (other.interpreterSearchPath!= null) {
return false;
}
} else {
if (!interpreterSearchPath.equals(other.interpreterSearchPath)) {
return false;
}
}
if (parseSearchPath == null) {
if (other.parseSearchPath!= null) {
return false;
@ -3340,6 +3426,7 @@ public class Settings
result = ((prime*result)+((interpreterDialect == null)? 0 :interpreterDialect.hashCode()));
result = ((prime*result)+((interpreterNameLookupCaseSensitivity == null)? 0 :interpreterNameLookupCaseSensitivity.hashCode()));
result = ((prime*result)+((interpreterLocale == null)? 0 :interpreterLocale.hashCode()));
result = ((prime*result)+((migrationAutoValidation == null)? 0 :migrationAutoValidation.hashCode()));
result = ((prime*result)+((locale == null)? 0 :locale.hashCode()));
result = ((prime*result)+((parseDialect == null)? 0 :parseDialect.hashCode()));
result = ((prime*result)+((parseLocale == null)? 0 :parseLocale.hashCode()));
@ -3350,6 +3437,7 @@ public class Settings
result = ((prime*result)+((parseIgnoreComments == null)? 0 :parseIgnoreComments.hashCode()));
result = ((prime*result)+((parseIgnoreCommentStart == null)? 0 :parseIgnoreCommentStart.hashCode()));
result = ((prime*result)+((parseIgnoreCommentStop == null)? 0 :parseIgnoreCommentStop.hashCode()));
result = ((prime*result)+((interpreterSearchPath == null)? 0 :interpreterSearchPath.hashCode()));
result = ((prime*result)+((parseSearchPath == null)? 0 :parseSearchPath.hashCode()));
return result;
}

View File

@ -35,13 +35,38 @@
*
*
*/
package org.jooq;
package org.jooq.exception;
import org.jooq.Migration;
/**
* The result of a {@link Migration}.
* An error occurred while running a {@link Migration}.
*
* @author Lukas Eder
*/
public interface MigrationResult {
public class DataMigrationException extends DataAccessException {
/**
* Generated UID
*/
private static final long serialVersionUID = -6460945824599280420L;
/**
* Constructor for DataMigrationException.
*
* @param message the detail message
*/
public DataMigrationException(String message) {
super(message);
}
/**
* Constructor for DataMigrationException.
*
* @param message the detail message
* @param cause the cause
*/
public DataMigrationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.exception;
import org.jooq.Migration;
/**
* An error occurred while running {@link Migration#validate()}.
*
* @author Lukas Eder
*/
public class DataMigrationValidationException extends DataAccessException {
/**
* Generated UID
*/
private static final long serialVersionUID = -6460945824599280420L;
/**
* Constructor for DataMigrationValidationException.
*
* @param message the detail message
*/
public DataMigrationValidationException(String message) {
super(message);
}
/**
* Constructor for DataMigrationValidationException.
*
* @param message the detail message
* @param cause the cause
*/
public DataMigrationValidationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -44,6 +44,8 @@ import static org.jooq.impl.Cascade.CASCADE;
import static org.jooq.impl.Cascade.RESTRICT;
import static org.jooq.impl.ConstraintType.FOREIGN_KEY;
import static org.jooq.impl.ConstraintType.PRIMARY_KEY;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.schema;
import static org.jooq.impl.SQLDataType.BIGINT;
import static org.jooq.impl.Tools.EMPTY_FIELD;
import static org.jooq.impl.Tools.intersect;
@ -93,6 +95,7 @@ import org.jooq.TableOptions.TableType;
import org.jooq.UniqueKey;
import org.jooq.Update;
import org.jooq.conf.InterpreterNameLookupCaseSensitivity;
import org.jooq.conf.InterpreterSearchSchema;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.DataDefinitionException;
import org.jooq.impl.ConstraintImpl.Action;
@ -109,7 +112,6 @@ final class DDLInterpreter {
private final Map<Name, MutableCatalog> catalogs = new LinkedHashMap<>();
private final MutableCatalog defaultCatalog;
private final MutableSchema defaultSchema;
private MutableSchema currentSchema;
// Caches
private final Map<Name, MutableCatalog.InterpretedCatalog> interpretedCatalogs = new HashMap<>();
@ -127,7 +129,6 @@ final class DDLInterpreter {
this.defaultCatalog = new MutableCatalog(NO_NAME);
this.catalogs.put(defaultCatalog.name(), defaultCatalog);
this.defaultSchema = new MutableSchema(NO_NAME, defaultCatalog);
this.currentSchema = defaultSchema;
}
final Meta meta() {
@ -273,10 +274,6 @@ final class DDLInterpreter {
mutableSchema.catalog.schemas.remove(mutableSchema);
else
throw schemaNotEmpty(schema);
// TODO: Is this needed?
if (mutableSchema.equals(currentSchema))
currentSchema = null;
}
private final void accept0(CreateTableImpl query) {
@ -1061,10 +1058,8 @@ final class DDLInterpreter {
}
private final MutableSchema getSchema(Schema input, boolean create) {
// TODO It does not appear we should auto-create schema in the interpreter. Why is this being done?
if (input == null)
return currentSchema;
return getInterpreterSearchPathSchema(create);
MutableCatalog catalog = defaultCatalog;
if (input.getCatalog() != null) {
@ -1084,6 +1079,16 @@ final class DDLInterpreter {
return schema;
}
private final MutableSchema getInterpreterSearchPathSchema(boolean create) {
List<InterpreterSearchSchema> searchPath = configuration.settings().getInterpreterSearchPath();
if (searchPath.isEmpty())
return defaultSchema;
InterpreterSearchSchema schema = searchPath.get(0);
return getSchema(schema(name(schema.getCatalog(), schema.getSchema())), create);
}
private final MutableTable newTable(
Table<?> table,
MutableSchema schema,

View File

@ -37,6 +37,9 @@
*/
package org.jooq.impl;
import static java.lang.Boolean.FALSE;
import static org.jooq.impl.DSL.createSchemaIfNotExists;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.HashMap;
@ -48,9 +51,9 @@ import org.jooq.Constants;
import org.jooq.ContextTransactionalCallable;
import org.jooq.Field;
import org.jooq.Identity;
import org.jooq.Meta;
import org.jooq.Migration;
import org.jooq.MigrationListener;
import org.jooq.MigrationResult;
import org.jooq.Name;
import org.jooq.Queries;
import org.jooq.Query;
@ -61,7 +64,8 @@ import org.jooq.TableField;
import org.jooq.UniqueKey;
import org.jooq.Version;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.DataDefinitionException;
import org.jooq.exception.DataMigrationException;
import org.jooq.exception.DataMigrationValidationException;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StopWatch;
@ -87,17 +91,10 @@ final class MigrationImpl extends AbstractScope implements Migration {
@Override
public final Version from() {
if (from == null) {
if (from == null)
// TODO: Use pessimistic locking so no one else can migrate in between
JooqMigrationsChangelogRecord currentRecord =
dsl().selectFrom(CHANGELOG)
.orderBy(CHANGELOG.MIGRATED_AT.desc(), CHANGELOG.ID.desc())
.limit(1)
.fetchOne();
from = currentRecord == null ? to().root() : versions().get(currentRecord.getMigratedTo());
}
from = currentVersion();
return from;
}
@ -126,16 +123,45 @@ final class MigrationImpl extends AbstractScope implements Migration {
return versions;
}
@Override
public final void validate() {
JooqMigrationsChangelogRecord currentRecord = currentChangelogRecord();
if (currentRecord != null) {
Version currentVersion = versions().get(currentRecord.getMigratedTo());
if (currentVersion == null)
throw new DataMigrationValidationException("Version trying to migrate to is not available from VersionProvider: " + currentRecord.getMigratedTo());
}
validateUnexpectedObjects();
}
private final void validateUnexpectedObjects() {
Version currentVersion = currentVersion();
Meta currentMeta = currentVersion.meta();
Meta existingMeta = dsl().meta();
for (Schema schema : existingMeta.getSchemas())
currentMeta = currentMeta.apply(createSchemaIfNotExists(schema));
System.out.println(existingMeta.migrateTo(currentMeta));
}
private static final MigrationResult MIGRATION_RESULT = new MigrationResult() {};
@Override
public final MigrationResult execute() throws DataDefinitionException {
public final MigrationResult execute() {
// TODO: Transactions don't really make sense in most dialects. In some, they do
// e.g. PostgreSQL supports transactional DDL. Check if we're getting this right.
return run(new ContextTransactionalCallable<MigrationResult>() {
@Override
public MigrationResult run() {
if (!FALSE.equals(dsl().settings().isMigrationAutoValidation()))
validate();
DefaultMigrationContext ctx = new DefaultMigrationContext(configuration(), from(), to(), queries());
MigrationListener listener = new MigrationListeners(configuration);
@ -147,6 +173,7 @@ final class MigrationImpl extends AbstractScope implements Migration {
return MIGRATION_RESULT;
}
// TODO: What to do if we're about to install things on a non-empty schema
// TODO: Implement preconditions
// TODO: Implement a listener with a variety of pro / oss features
// TODO: Implement additional out-of-the-box sanity checks
@ -230,7 +257,7 @@ final class MigrationImpl extends AbstractScope implements Migration {
public final void init() {
// TODO: What to do when initialising jOOQ-migrations on an existing database?
// - Should there be init() commands that can be run explicitl by the user?
// - Should there be init() commands that can be run explicitly by the user?
// - Will we reverse engineer the production Meta snapshot first?
if (!existsChangelog())
dsl().meta(CHANGELOG).ddl().executeBatch();
@ -248,9 +275,31 @@ final class MigrationImpl extends AbstractScope implements Migration {
return false;
}
private final JooqMigrationsChangelogRecord currentChangelogRecord() {
return existsChangelog()
? dsl().selectFrom(CHANGELOG)
.orderBy(CHANGELOG.MIGRATED_AT.desc(), CHANGELOG.ID.desc())
.limit(1)
.fetchOne()
: null;
}
private final Version currentVersion() {
JooqMigrationsChangelogRecord currentRecord = currentChangelogRecord();
return currentRecord == null ? to().root() : versions().get(currentRecord.getMigratedTo());
}
private final <T> T run(final ContextTransactionalCallable<T> runnable) {
init();
return dsl().transactionResult(runnable);
try {
init();
return dsl().transactionResult(runnable);
}
catch (DataMigrationException e) {
throw e;
}
catch (Exception e) {
throw new DataMigrationException("Exception during migration", e);
}
}
// -------------------------------------------------------------------------

View File

@ -206,7 +206,7 @@ final class QualifiedName extends AbstractName {
@Override
public final Name unqualifiedName() {
if (qualifiedName.length <= 1)
if (qualifiedName.length == 0)
return this;
else
return qualifiedName[qualifiedName.length - 1];

View File

@ -399,6 +399,14 @@ jOOQ queries, for which no specific fetchSize value was specified.]]></jxb:javad
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[The Locale to be used with any interpreter locale dependent logic, defaulting to {@link #getLocale()}.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="interpreterSearchPath" type="jooq-runtime:InterpreterSearchSchemata" minOccurs="0" maxOccurs="1">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[[#9677] The search path to be used for unqualified table lookups by the interpreter.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="migrationAutoValidation" type="boolean" minOccurs="0" maxOccurs="1" default="true">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Whether a migration automatically runs a validation first.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="locale" type="string" minOccurs="0" maxOccurs="1">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[The Locale to be used with any locale dependent logic if there is not a more specific locale available. More specific locales include e.g. {@link #getRenderLocale()}, {@link #getParseLocale()}, or {@link #getInterpreterLocale()}.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
@ -463,6 +471,24 @@ jOOQ queries, for which no specific fetchSize value was specified.]]></jxb:javad
</all>
</complexType>
<complexType name="InterpreterSearchSchemata">
<sequence>
<element name="schema" type="jooq-runtime:InterpreterSearchSchema" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
<complexType name="InterpreterSearchSchema">
<annotation><appinfo><jxb:class><jxb:javadoc><![CDATA[A schema that is on the search path.]]></jxb:javadoc></jxb:class></appinfo></annotation>
<all>
<element name="catalog" type="string" minOccurs="0" maxOccurs="1">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[The catalog qualifier of the schema, if applicable.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="schema" type="string" minOccurs="1" maxOccurs="1">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[The schema qualifier whose elements can be found from the search path.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
</all>
</complexType>
<complexType name="RenderMapping">
<annotation><appinfo><jxb:class><jxb:javadoc><![CDATA[The runtime schema and table mapping.]]></jxb:javadoc></jxb:class></appinfo></annotation>