org.jooq.meta.extensions.ddl.DDLDatabase Maven / Gradle / Ivy
/*
* 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
*
* https://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
* Apache-2.0 license and offer limited warranties, support, maintenance, and
* commercial database integrations.
*
* For more information, please visit: https://www.jooq.org/legal/licensing
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.meta.extensions.ddl;
// ...
import static org.jooq.conf.SettingsTools.renderLocale;
import static org.jooq.impl.DSL.name;
import static org.jooq.tools.StringUtils.isBlank;
import java.io.File;
import java.io.Reader;
import java.sql.SQLException;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jooq.DSLContext;
import org.jooq.FilePattern;
import org.jooq.FilePattern.Sort;
import org.jooq.Name;
import org.jooq.Name.Quoted;
import org.jooq.Queries;
import org.jooq.Query;
import org.jooq.ResultQuery;
import org.jooq.Source;
import org.jooq.VisitContext;
import org.jooq.VisitListener;
import org.jooq.conf.ParseUnknownFunctions;
import org.jooq.conf.Settings;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.jooq.impl.ParserException;
import org.jooq.meta.extensions.AbstractInterpretingDatabase;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.jdbc.JDBCUtils;
import org.h2.api.ErrorCode;
/**
* The DDL database.
*
* This meta data source parses a set of SQL scripts, translates them to the H2
* dialect and runs them on an in-memory H2 database before reverse engineering
* the outcome.
*
* The SQL scripts are located in the scripts
scripts property
* available from {@link #getProperties()}.
*
* @author Lukas Eder
*/
public class DDLDatabase extends AbstractInterpretingDatabase {
private static final JooqLogger log = JooqLogger.getLogger(DDLDatabase.class);
private static final Pattern P_NAME = Pattern.compile("(?s:.*?\"([^\"]*)\".*)");
private boolean logExecutedQueries = true;
private boolean logExecutionResults = true;
@Override
protected void export() throws Exception {
Settings defaultSettings = new Settings();
String scripts = getProperties().getProperty("scripts");
String sql = getProperties().getProperty("sql");
String encoding = getProperties().getProperty("encoding", "UTF-8");
String sort = getProperties().getProperty("sort", "semantic").toLowerCase();
final String defaultNameCase = getProperties().getProperty("defaultNameCase", "as_is").toUpperCase();
boolean parseIgnoreComments = !"false".equalsIgnoreCase(getProperties().getProperty("parseIgnoreComments"));
String parseIgnoreCommentStart = getProperties().getProperty("parseIgnoreCommentStart", defaultSettings.getParseIgnoreCommentStart());
String parseIgnoreCommentStop = getProperties().getProperty("parseIgnoreCommentStop", defaultSettings.getParseIgnoreCommentStop());
logExecutedQueries = !"false".equalsIgnoreCase(getProperties().getProperty("logExecutedQueries"));
logExecutionResults = !"false".equalsIgnoreCase(getProperties().getProperty("logExecutionResults"));
if (isBlank(scripts) && isBlank(sql)) {
scripts = "";
sql = "";
log.warn("No scripts defined", "It is recommended that you provide an explicit script directory to scan");
}
try {
final DSLContext ctx = DSL.using(connection(), new Settings()
.withParseIgnoreComments(parseIgnoreComments)
.withParseIgnoreCommentStart(parseIgnoreCommentStart)
.withParseIgnoreCommentStop(parseIgnoreCommentStop)
.withParseUnknownFunctions(ParseUnknownFunctions.IGNORE)
);
// [#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);
if (!"AS_IS".equals(defaultNameCase)) {
ctx.configuration().set(new VisitListener() {
@Override
public void visitStart(VisitContext vc) {
if (vc.queryPart() instanceof Name n) {
Name[] parts = n.parts();
boolean changed = false;
for (int i = 0; i < parts.length; i++) {
// [#9931] [#12752] This explicitly excludes the new Quoted.SYSTEM
// flag for DSL.systemName() names
if (parts[i].quoted() == Quoted.UNQUOTED) {
parts[i] = DSL.quotedName(
"UPPER".equals(defaultNameCase)
? parts[i].first().toUpperCase(renderLocale(ctx.settings()))
: parts[i].first().toLowerCase(renderLocale(ctx.settings()))
);
changed = true;
}
}
if (changed)
vc.queryPart(DSL.name(parts));
}
}
});
}
if (!isBlank(sql))
load(ctx, Source.of(sql));
if (!isBlank(scripts))
new FilePattern()
.encoding(encoding)
.basedir(new File(getBasedir()))
.pattern(scripts)
.sort(Sort.of(sort))
.load(source -> DDLDatabase.this.load(ctx, source));
}
catch (ParserException e) {
log.error("An exception occurred while parsing script source : " + scripts + ". Please report this error to https://jooq.org/bug", e);
throw e;
}
}
private void load(DSLContext ctx, Source source) {
Reader r = null;
try {
Scanner s = new Scanner(r = source.reader()).useDelimiter("\\A");
Queries queries = ctx.parser().parse(s.hasNext() ? s.next() : "");
for (Query query : queries) {
repeat:
for (;;) {
try {
if (logExecutedQueries)
log.info(query);
if (logExecutedQueries && logExecutionResults)
if (query instanceof ResultQuery)
log.info("\n" + ((ResultQuery>) query).fetch());
else
log.info("Update count: " + query.execute());
// [#10008] Execute all queries. Could have FOR UPDATE or other side effects
else
query.execute();
break repeat;
}
catch (DataAccessException e) {
// [#7039] Auto create missing schemas. We're using the
if (Integer.toString(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 (DataAccessException e) {
// [#9138] Make users aware of the new parse ignore comment syntax
log.error("DDLDatabase Error", "Your SQL string could not be parsed or interpreted. This may have a variety of reasons, including:\n"
+ "- The jOOQ parser doesn't understand your SQL\n"
+ "- The jOOQ DDL simulation logic (translating to H2) cannot simulate your SQL\n"
+ "\n"
+ "If you think this is a bug or a feature worth requesting, please report it here: https://jooq.org/bug\n"
+ "\n"
+ "As a workaround, you can use the Settings.parseIgnoreComments syntax documented here:\n"
+ "https://www.jooq.org/doc/latest/manual/sql-building/dsl-context/custom-settings/settings-parser/");
throw e;
}
finally {
JDBCUtils.safeClose(r);
}
}
}