All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jooq.impl.DefaultExecuteContext Maven / Gradle / Ivy

There is a newer version: 3.19.15
Show newest version
/*
 * 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.impl;

import static java.lang.Boolean.TRUE;
// ...
import static org.jooq.conf.SettingsTools.renderLocale;
import static org.jooq.impl.Tools.EMPTY_INT;
import static org.jooq.impl.Tools.EMPTY_PARAM;
import static org.jooq.impl.Tools.EMPTY_QUERY;
import static org.jooq.impl.Tools.EMPTY_STRING;

import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLOutput;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jooq.Configuration;
import org.jooq.ConnectionProvider;
import org.jooq.Constants;
import org.jooq.ConverterContext;
import org.jooq.DDLQuery;
import org.jooq.DSLContext;
import org.jooq.Delete;
import org.jooq.ExecuteContext;
import org.jooq.ExecuteListener;
import org.jooq.ExecuteType;
import org.jooq.Insert;
import org.jooq.Merge;
import org.jooq.Param;
// ...
import org.jooq.Query;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.ResultQuery;
import org.jooq.Routine;
import org.jooq.SQLDialect;
import org.jooq.Scope;
import org.jooq.Update;
import org.jooq.conf.DiagnosticsConnection;
import org.jooq.conf.Settings;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.jdbc.JDBCUtils;
import org.jooq.tools.reflect.Reflect;

import org.jetbrains.annotations.NotNull;

// ...

/**
 * A default implementation for the {@link ExecuteContext}.
 *
 * @author Lukas Eder
 */
class DefaultExecuteContext implements ExecuteContext {

    private static final JooqLogger                       log               = JooqLogger.getLogger(DefaultExecuteContext.class);
    private static final JooqLogger                       logVersionSupport = JooqLogger.getLogger(DefaultExecuteContext.class, "logVersionSupport", 1);
    private static final JooqLogger                       logDefaultDialect = JooqLogger.getLogger(DefaultExecuteContext.class, "logDefaultDialect", 1);

    // Persistent attributes (repeatable)
    private final ConverterContext                        converterContext;
    private final Instant                                 creationTime;
    private final Configuration                           originalConfiguration;
    private final Configuration                           derivedConfiguration;
    private final Map                     data;






    private Query                                         query;
    private final Routine                              routine;
    private String                                        sql;
    private Param[]                                    params;
    private int                                           skipUpdateCounts;

    private final BatchMode                               batchMode;
    private Query[]                                       batchQueries;
    private String[]                                      batchSQL;
    private int[]                                         batchRows;

    ConnectionProvider                                    connectionProvider;
    private Connection                                    connection;
    private Connection                                    wrappedConnection;
    private PreparedStatement                             statement;
    private int                                           statementExecutionCount;
    private ResultSet                                     resultSet;
    private Record                                        record;
    private Result                                     result;
    int                                                   recordLevel;
    int                                                   resultLevel;
    private int                                           rows      = -1;
    private RuntimeException                              exception;
    private SQLException                                  sqlException;
    private SQLWarning                                    sqlWarning;
    private String[]                                      serverOutput;

    // ------------------------------------------------------------------------
    // XXX: Static utility methods for handling blob / clob lifecycle
    // ------------------------------------------------------------------------

    private static final ThreadLocal> RESOURCES = new ThreadLocal<>();

    /**
     * Clean up blobs, clobs and the local configuration.
     * 

*

BLOBS and CLOBS
*

* [#1326] This is necessary in those dialects that have long-lived * temporary lob objects, which can cause memory leaks in certain contexts, * where the lobs' underlying session / connection is long-lived as well. * Specifically, Oracle and ojdbc have some trouble when streaming temporary * lobs to UDTs: *

    *
  1. The lob cannot have a call-scoped life time with UDTs
  2. *
  3. Freeing the lob after binding will cause an ORA-22275
  4. *
  5. Not freeing the lob after execution will cause an * {@link OutOfMemoryError}
  6. *
*

*

Local configuration
*

* [#1544] There exist some corner-cases regarding the {@link SQLOutput} * API, used for UDT serialisation / deserialisation, which have no elegant * solutions of obtaining a {@link Configuration} and thus a JDBC * {@link Connection} object short of: *

    *
  • Making assumptions about the JDBC driver and using proprietary API, * e.g. that of ojdbc
  • *
  • Dealing with this problem globally by using such a local * configuration
  • *
* * @see http://stackoverflow.com/q/11439543/521799 */ static final void clean() { List resources = RESOURCES.get(); if (resources != null) { for (AutoCloseable resource : resources) JDBCUtils.safeClose(resource); RESOURCES.remove(); } LOCAL_CONNECTION.remove(); } /** * Register a blob for later cleanup with {@link #clean()} */ static final void register(Blob blob) { register((AutoCloseable) blob::free); } /** * Register a clob for later cleanup with {@link #clean()} */ static final void register(Clob clob) { register((AutoCloseable) clob::free); } /** * Register an xml for later cleanup with {@link #clean()} */ static final void register(SQLXML xml) { register((AutoCloseable) xml::free); } /** * Register an array for later cleanup with {@link #clean()} */ static final void register(Array array) { register((AutoCloseable) array::free); } /** * Register a closeable for later cleanup with {@link #clean()} */ static final void register(AutoCloseable closeable) { List list = RESOURCES.get(); if (list == null) { list = new ArrayList<>(); RESOURCES.set(list); } list.add(closeable); } // ------------------------------------------------------------------------ // XXX: Static utility methods for handling Configuration lifecycle // ------------------------------------------------------------------------ private static final ThreadLocal LOCAL_EXECUTE_CONTEXT = new ThreadLocal<>(); /** * Get the registered {@link ExecuteContext}. *

* It can be safely assumed that such a configuration is available once the * {@link ExecuteContext} has been established, until the statement is * closed. */ static final ExecuteContext localExecuteContext() { return LOCAL_EXECUTE_CONTEXT.get(); } /** * Run a runnable with a new {@link #localExecuteContext()}. */ static final void localExecuteContext(ExecuteContext ctx, ThrowingRunnable runnable) throws E { localExecuteContext(ctx, () -> { runnable.run(); return null; }); } /** * Run a supplier with a new {@link #localExecuteContext()}. */ static final T localExecuteContext(ExecuteContext ctx, ThrowingSupplier supplier) throws E { ExecuteContext old = localExecuteContext(); try { LOCAL_EXECUTE_CONTEXT.set(ctx); return supplier.get(); } finally { LOCAL_EXECUTE_CONTEXT.set(old); } } // ------------------------------------------------------------------------ // XXX: Static utility methods for handling Configuration lifecycle // ------------------------------------------------------------------------ private static final ThreadLocal LOCAL_CONNECTION = new ThreadLocal<>(); /** * Get the registered connection. *

* It can be safely assumed that such a connection is available once the * {@link ExecuteContext} has been established, until the statement is * closed. */ static final Connection localConnection() { return LOCAL_CONNECTION.get(); } /** * Get the registered connection's "target connection" through * {@link Configuration#unwrapperProvider()} if applicable. *

* It can be safely assumed that such a connection is available once the * {@link ExecuteContext} has been established, until the statement is * closed. */ static final Connection localTargetConnection(Scope scope) { Connection result = localConnection(); log.info("Could not unwrap native Connection type. Consider implementing an org.jooq.UnwrapperProvider"); return result; } // ------------------------------------------------------------------------ // XXX: Constructors // ------------------------------------------------------------------------ DefaultExecuteContext(Configuration configuration) { this(configuration, BatchMode.NONE, null, null, null); } DefaultExecuteContext(Configuration configuration, BatchMode batchMode, Query[] batchQueries) { this(configuration, batchMode, null, batchQueries, null); } DefaultExecuteContext(Configuration configuration, Query query) { this(configuration, BatchMode.NONE, query, null, null); } DefaultExecuteContext(Configuration configuration, Routine routine) { this(configuration, BatchMode.NONE, null, null, routine); } private DefaultExecuteContext(Configuration configuration, BatchMode batchMode, Query query, Query[] batchQueries, Routine routine) { // [#4277] The ExecuteContext's Configuration will always return the same Connection, // e.g. when running statements from sub-ExecuteContexts // [#7569] The original configuration is attached to Record and Result instances this.creationTime = configuration.clock().instant(); this.connectionProvider = configuration.connectionProvider(); this.originalConfiguration = configuration; this.derivedConfiguration = configuration.derive(new ExecuteContextConnectionProvider()); this.data = new DataMap(); this.batchMode = batchMode; this.query = query; this.routine = routine; this.converterContext = new DefaultConverterContext(derivedConfiguration, data); batchQueries0(batchQueries); clean(); } @Override public final ConverterContext converterContext() { return converterContext; } @Override public final Instant creationTime() { return creationTime; } @Override public final Map data() { return data; } @Override public final Object data(Object key) { return data.get(key); } @Override public final Object data(Object key, Object value) { return data.put(key, value); } @Override public final ExecuteType type() { // This can only be a routine if (routine != null) { return ExecuteType.ROUTINE; } // This can only be a BatchSingle or BatchMultiple execution else if (batchMode != BatchMode.NONE) { return ExecuteType.BATCH; } // Any other type of query else if (query != null) { if (query instanceof ResultQuery) { return ExecuteType.READ; } else if (query instanceof Insert || query instanceof Update || query instanceof Delete || query instanceof Merge) { return ExecuteType.WRITE; } else if (query instanceof DDLQuery) { return ExecuteType.DDL; } // Analyse SQL in plain SQL queries: else { String s = query.getSQL().toLowerCase(renderLocale(configuration().settings())); // TODO: Use a simple lexer to parse SQL here. Potentially, the // SQL Console's SQL formatter could be used...? if (s.matches("^(with\\b.*?\\bselect|select|explain)\\b.*?")) return ExecuteType.READ; // These are sample DML statements. There may be many more else if (s.matches("^(insert|update|delete|merge|replace|upsert|lock)\\b.*?")) return ExecuteType.WRITE; // These are only sample DDL statements. There may be many more else if (s.matches("^(create|alter|drop|truncate|grant|revoke|analyze|comment|flashback|enable|disable)\\b.*?")) return ExecuteType.DDL; // JDBC escape syntax for routines else if (s.matches("^\\s*\\{\\s*(\\?\\s*=\\s*)call.*?")) return ExecuteType.ROUTINE; // Vendor-specific calling of routines / procedural blocks else if (s.matches("^(call|begin|declare)\\b.*?")) return ExecuteType.ROUTINE; } } // Fetching JDBC result sets, e.g. with SQL.fetch(ResultSet) else if (resultSet != null) { return ExecuteType.READ; } // No query available return ExecuteType.OTHER; } @Override public final Query query() { return query; } @Override public final BatchMode batchMode() { return batchMode; } @Override public final Query[] batchQueries() { return batchMode != BatchMode.NONE ? batchQueries : query() != null ? new Query[] { query() } : EMPTY_QUERY; } private final void batchQueries0(Query... newQueries) { if (newQueries != null) { this.batchQueries = newQueries.clone(); this.batchSQL = new String[newQueries.length]; this.batchRows = new int[newQueries.length]; Arrays.fill(this.batchRows, -1); } else { this.batchQueries = null; this.batchSQL = null; this.batchRows = null; } } @Override public final Routine routine() { return routine; } @Override public final void sql(String s) { this.sql = s; // If this isn't a BatchMultiple query if (batchSQL != null && batchSQL.length == 1) batchSQL[0] = s; } @Override public final String sql() { return sql; } // [#16215] [#16217] These methods are backported without being declared in public API public void params(Param[] p) { this.params = p; } public final Param[] params() { return params != null ? params : EMPTY_PARAM; } @Override public final int skipUpdateCounts() { return this.skipUpdateCounts; } @Override public void skipUpdateCounts(int skip) { this.skipUpdateCounts = skip; } @Override public final String[] batchSQL() { return batchMode != BatchMode.NONE ? batchSQL : routine != null || query() != null ? new String[] { sql } : EMPTY_STRING; } @Override public final void statement(PreparedStatement s) { this.statement = s; } @Override public final PreparedStatement statement() { return statement; } @Override public final int statementExecutionCount() { return statementExecutionCount; } @Override public final void resultSet(ResultSet rs) { this.resultSet = rs; } @Override public final ResultSet resultSet() { return resultSet; } @Override public final Configuration configuration() { return derivedConfiguration; } // [#4277] [#7569] The original configuration that was used to create the // derived configuration in this ExecuteContext final Configuration originalConfiguration() { return originalConfiguration; } @Override public final DSLContext dsl() { return configuration().dsl(); } @Override public final Settings settings() { return configuration().settings(); } @Override public final SQLDialect dialect() { return configuration().dialect(); } @Override public final SQLDialect family() { return configuration().family(); } @Override public final void connectionProvider(ConnectionProvider provider) { this.connectionProvider = provider; } @Override public final Connection connection() { // All jOOQ internals are expected to get a connection through this // single method. It can thus be guaranteed, that every connection is // wrapped by a ConnectionProxy, transparently, in order to implement // Settings.getStatementType() correctly. if (wrappedConnection == null && connectionProvider != null) connection(connectionProvider, connectionProvider.acquire()); return wrappedConnection; } /** * Initialise this {@link DefaultExecuteContext} with a pre-existing * {@link Connection}. *

* [#3191] This is needed, e.g. when using * {@link Query#keepStatement(boolean)}. */ final void connection(ConnectionProvider provider, Connection c) { if (c != null) { // [#11355] Check configured dialect version vs. JDBC Connection server version. if (dialect().isVersioned() && logVersionSupport.isWarnEnabled()) { String productVersion = null; try { int majorVersion = c.getMetaData().getDatabaseMajorVersion(); int minorVersion = c.getMetaData().getDatabaseMinorVersion(); productVersion = c.getMetaData().getDatabaseProductVersion(); logVersionSupport(majorVersion, minorVersion, productVersion); } // [#14833] There are various reasons why the version can't be read, which we can ignore catch (SQLException e) { logVersionSupport.info("Version", "Database version cannot be read: " + e.getMessage()); } // [#14791] Could also be NumberFormatException when reading non-standard version numbers catch (Exception e) { logVersionSupport.info("Version", "Cannot obtain database version for " + dialect() + ": " + productVersion + ". (" + e.getClass() + ": " + e.getMessage() + "). Please consider reporting this here: https://jooq.org/bug"); } } LOCAL_CONNECTION.set(c); connection = c; wrappedConnection = wrap(provider, c); } } private final void logVersionSupport(int majorVersion, int minorVersion, String productVersion) { if (!dialect().supportsDatabaseVersion(majorVersion, minorVersion, productVersion)) logVersionSupport.warn("Version mismatch", "Database version is older than what dialect " + dialect() + " supports: " + productVersion + ". Consider https://www.jooq.org/download/support-matrix to see what jOOQ version and edition supports which RDBMS versions."); else logVersionSupport.info("Version", "Database version is supported by dialect " + dialect() + ": " + productVersion); } private final Connection wrap(ConnectionProvider provider, Connection c) { return wrap0(new SettingsEnabledConnection(new ProviderEnabledConnection(provider, c), derivedConfiguration.settings(), this)); } private final Connection wrap0(Connection c) { if (derivedConfiguration.settings().getDiagnosticsConnection() == DiagnosticsConnection.ON) return new org.jooq.impl.DiagnosticsConnection(derivedConfiguration, c); else return c; } final void incrementStatementExecutionCount() { statementExecutionCount++; } final DefaultExecuteContext withStatementExecutionCount(int count) { statementExecutionCount = count; return this; } @Override public final void record(Record r) { this.record = r; } @Override public final Record record() { return record; } @Override public final int recordLevel() { return recordLevel; } @Override public final int rows() { return rows; } @Override public final void rows(int r) { this.rows = r; // If this isn't a BatchMultiple query if (batchRows != null && batchRows.length == 1) batchRows[0] = r; } @Override public final int[] batchRows() { return batchMode != BatchMode.NONE ? batchRows : routine != null || query() != null ? new int[] { rows } : EMPTY_INT; } @Override public final void result(Result r) { this.result = r; } @Override public final Result result() { return result; } @Override public final int resultLevel() { return resultLevel; } @Override public final RuntimeException exception() { return exception; } @Override public final void exception(RuntimeException e) { this.exception = Tools.translate(sql(), e); if (Boolean.TRUE.equals(settings().isDebugInfoOnStackTrace())) { // [#5570] Add jOOQ version and SQL Dialect info on the stack trace // to help users write better bug reports. // See http://stackoverflow.com/q/39712695/521799 StackTraceElement[] oldStack = exception.getStackTrace(); if (oldStack != null) { StackTraceElement[] newStack = new StackTraceElement[oldStack.length + 1]; System.arraycopy(oldStack, 0, newStack, 1, oldStack.length); newStack[0] = new StackTraceElement( "org.jooq_" + Constants.VERSION + "." + dialect(), "debug", null, -1); exception.setStackTrace(newStack); } } } @Override public final SQLException sqlException() { return sqlException; } @Override public final void sqlException(SQLException e) { this.sqlException = e; exception(Tools.translate(sql(), e)); if (family() == SQLDialect.DEFAULT && logDefaultDialect.isWarnEnabled()) logDefaultDialect.warn("Unsupported dialect", """ An exception was thrown when executing a query with unsupported dialect: SQLDialect.DEFAULT. This is usually due to one of 2 reasons: - The dialect was configured by accident (e.g. through a wrong Spring Boot configuration). In this case, the solution is to configure the correct dialect, e.g. SQLDialect.POSTGRES - SQLDialect.DEFAULT is used as a "close enough" approximation of an unsupported dialect. Please beware that SQLDialect.DEFAULT is used mainly for DEBUG logging SQL strings, e.g. when calling Query.toString(). It does not guarantee stability of generated SQL, i.e. future versions of jOOQ may produce different SQL strings, which may break assumptions about your unsupported dialect. Please visit https://github.com/jOOQ/jOOQ/discussions/14059 for new dialect support. """ ); } @Override public final SQLWarning sqlWarning() { return sqlWarning; } @Override public final void sqlWarning(SQLWarning e) { this.sqlWarning = e; } @Override public final String[] serverOutput() { return serverOutput == null ? EMPTY_STRING : serverOutput; } @Override public final void serverOutput(String[] output) { this.serverOutput = output; } final class ExecuteContextConnectionProvider implements ConnectionProvider { @NotNull @Override public final Connection acquire() { // [#4277] Connections are acquired lazily in parent ExecuteContext. A child ExecuteContext // may well need a Connection earlier than the parent, in case of which acquisition is // forced in the parent as well. if (connection == null) DefaultExecuteContext.this.connection(); return wrap(this, connection); } @Override public final void release(Connection c) {} } final void transformQueries(ExecuteListener listener) { if (TRUE.equals(settings().isTransformPatterns()) && configuration().requireCommercial(() -> "SQL transformations are a commercial only feature. Please consider upgrading to the jOOQ Professional Edition or jOOQ Enterprise Edition.")) { } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy