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

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

There is a newer version: 3.19.11
Show newest version
/**
 * Copyright (c) 2009-2016, Data Geekery GmbH (http://www.datageekery.com)
 * All rights reserved.
 *
 * 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 java.lang.Boolean.FALSE;
import static java.lang.Character.isJavaIdentifierPart;
import static java.util.Arrays.asList;
// ...
import static org.jooq.SQLDialect.CUBRID;
import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.conf.BackslashEscaping.DEFAULT;
import static org.jooq.conf.BackslashEscaping.ON;
import static org.jooq.conf.ParamType.INLINED;
import static org.jooq.conf.ParamType.NAMED;
import static org.jooq.conf.ParamType.NAMED_OR_INLINED;
import static org.jooq.conf.SettingsTools.getBackslashEscaping;
import static org.jooq.conf.SettingsTools.reflectionCaching;
import static org.jooq.conf.SettingsTools.updatablePrimaryKeys;
import static org.jooq.impl.DDLStatementType.CREATE_INDEX;
import static org.jooq.impl.DDLStatementType.CREATE_SEQUENCE;
import static org.jooq.impl.DDLStatementType.CREATE_TABLE;
import static org.jooq.impl.DDLStatementType.CREATE_VIEW;
import static org.jooq.impl.DDLStatementType.DROP_INDEX;
import static org.jooq.impl.DDLStatementType.DROP_SEQUENCE;
import static org.jooq.impl.DDLStatementType.DROP_TABLE;
import static org.jooq.impl.DDLStatementType.DROP_VIEW;
import static org.jooq.impl.DSL.concat;
import static org.jooq.impl.DSL.escape;
import static org.jooq.impl.DSL.getDataType;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.nullSafe;
import static org.jooq.impl.DSL.val;
import static org.jooq.impl.DefaultExecuteContext.localConnection;
import static org.jooq.impl.Identifiers.QUOTES;
import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER;
import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER_ESCAPED;
import static org.jooq.impl.Identifiers.QUOTE_START_DELIMITER;
import static org.jooq.tools.reflect.Reflect.accessible;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinPool.ManagedBlocker;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

// ...
import org.jooq.Attachable;
import org.jooq.AttachableInternal;
import org.jooq.BindContext;
import org.jooq.Catalog;
import org.jooq.Clause;
import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.Context;
import org.jooq.Cursor;
import org.jooq.DSLContext;
import org.jooq.DataType;
import org.jooq.EnumType;
import org.jooq.ExecuteContext;
import org.jooq.ExecuteListener;
import org.jooq.Field;
import org.jooq.Name;
import org.jooq.Param;
import org.jooq.QueryPart;
import org.jooq.Record;
import org.jooq.RecordType;
import org.jooq.RenderContext;
import org.jooq.RenderContext.CastMode;
import org.jooq.Result;
import org.jooq.Results;
import org.jooq.Row;
import org.jooq.SQLDialect;
import org.jooq.Schema;
import org.jooq.SelectField;
import org.jooq.Table;
import org.jooq.UDT;
import org.jooq.UDTRecord;
import org.jooq.UpdatableRecord;
import org.jooq.conf.BackslashEscaping;
import org.jooq.conf.Settings;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.MappingException;
import org.jooq.exception.TooManyRowsException;
import org.jooq.impl.Tools.Cache.CachedOperation;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
import org.jooq.tools.jdbc.JDBCUtils;
import org.jooq.tools.reflect.Reflect;
import org.jooq.types.UByte;
import org.jooq.types.UInteger;
import org.jooq.types.ULong;
import org.jooq.types.UShort;

/**
 * General internal jOOQ utilities
 *
 * @author Lukas Eder
 */
final class Tools {

    static final JooqLogger log = JooqLogger.getLogger(Tools.class);

    // ------------------------------------------------------------------------
    // Some constants for use with Context.data()
    // ------------------------------------------------------------------------

    enum DataKey {

        /**
         * [#1537] This constant is used internally by jOOQ to omit the RETURNING
         * clause in {@link DSLContext#batchStore(UpdatableRecord...)} calls for
         * {@link SQLDialect#POSTGRES}.
         */
        DATA_OMIT_RETURNING_CLAUSE,

        /**
         * [#1905] This constant is used internally by jOOQ to indicate to
         * subqueries that they're being rendered in the context of a row value
         * expression predicate.
         * 

* This is particularly useful for H2, which pretends that ARRAYs and RVEs * are the same */ DATA_ROW_VALUE_EXPRESSION_PREDICATE_SUBQUERY, /** * [#1296] This constant is used internally by jOOQ to indicate that * {@link ResultSet} rows must be locked to emulate a * FOR UPDATE clause. */ DATA_LOCK_ROWS_FOR_UPDATE, /** * [#1520] Count the number of bind values, and potentially enforce a static * statement. */ DATA_COUNT_BIND_VALUES, /** * [#1520] Enforce executing static statements. *

* Some SQL dialects support only a limited amount of bind variables. This * flag is set when static statements have too many bind variables. Known * values are: *

    *
  • {@link SQLDialect#ASE} : 2000
  • *
  • {@link SQLDialect#INGRES} : 1024
  • *
  • {@link SQLDialect#SQLITE} : 999
  • *
  • {@link SQLDialect#SQLSERVER} : 2100
  • *
*/ DATA_FORCE_STATIC_STATEMENT, /** * [#2665] Omit the emission of clause events by {@link QueryPart}s. *

* Some {@link QueryPart}s may contain further {@link QueryPart}s for whom * {@link Clause} emission should be avoided. For example * {@link Clause#FIELD_REFERENCE} may contain a * {@link Clause#TABLE_REFERENCE}. */ DATA_OMIT_CLAUSE_EVENT_EMISSION, /** * [#2665] Wrap derived tables in parentheses. *

* Before allowing for hooking into the SQL transformation SPI, new * {@link RenderContext} instances could be created to "try" to render a * given SQL subclause before inserting it into the real SQL string. This * practice should no longer be pursued, as such "sub-renderers" will emit / * divert {@link Clause} events. */ DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES, /** * [#2790] A locally scoped data map. *

* Sometimes, it is useful to have some information only available while * visiting QueryParts in the same context of the current subquery, e.g. * when communicating between SELECT and WINDOW clauses, as is required to * emulate #531. */ DATA_LOCALLY_SCOPED_DATA_MAP, /** * [#531] The local window definitions. *

* The window definitions declared in the WINDOW clause are * needed in the SELECT clause when emulating them by inlining * window specifications. */ DATA_WINDOW_DEFINITIONS, /** * [#1629] The {@link Connection#getAutoCommit()} flag value before starting * a new transaction. */ DATA_DEFAULT_TRANSACTION_PROVIDER_AUTOCOMMIT, /** * [#1629] The {@link Connection#getAutoCommit()} flag value before starting * a new transaction. */ DATA_DEFAULT_TRANSACTION_PROVIDER_SAVEPOINTS, /** * [#1629] The {@link DefaultConnectionProvider} instance to be used during * the transaction. */ DATA_DEFAULT_TRANSACTION_PROVIDER_CONNECTION, /** * [#2080] When emulating OFFSET pagination in certain databases, synthetic * aliases are generated that must be referenced also in * ORDER BY clauses, in lieu of their corresponding original * aliases. */ DATA_OVERRIDE_ALIASES_IN_ORDER_BY, /** * [#2080] When emulating OFFSET pagination in certain databases, synthetic * aliases are generated that must be referenced also in * ORDER BY clauses, in lieu of their corresponding original * aliases. */ DATA_UNALIAS_ALIASES_IN_ORDER_BY, /** * [#3381] The table to be used for the {@link Clause#SELECT_INTO} clause. */ DATA_SELECT_INTO_TABLE, /** * [#3381] Omit the {@link Clause#SELECT_INTO}, as it is being emulated. */ DATA_OMIT_INTO_CLAUSE, /** * [#1658] Specify whether the trailing LIMIT clause needs to be rendered. */ DATA_RENDER_TRAILING_LIMIT_IF_APPLICABLE, /** * [#3886] Whether a list has already been indented. */ DATA_LIST_ALREADY_INDENTED, /** * [#3338] [#5086] Whether a constraint is being referenced (rather than * declared). */ DATA_CONSTRAINT_REFERENCE, /** * [#1206] Whether to collect Semi / Anti JOIN. */ DATA_COLLECT_SEMI_ANTI_JOIN, /** * [#1206] The collected Semi / Anti JOIN predicates. */ DATA_COLLECTED_SEMI_ANTI_JOIN, /** * [#2995] An INSERT INTO t SELECT statement. Without any * explicit column list, the SELECT statement must not be * wrapped in parentheses (which would be interpreted as the column * list's parentheses). */ DATA_INSERT_SELECT_WITHOUT_INSERT_COLUMN_LIST, } /** * [#2965] These are {@link ConcurrentHashMap}s containing caches for * reflection information. *

* new String() is used to allow for synchronizing on these * objects. */ static final String DATA_REFLECTION_CACHE_GET_ANNOTATED_GETTER = new String("org.jooq.configuration.reflection-cache.get-annotated-getter"); static final String DATA_REFLECTION_CACHE_GET_ANNOTATED_MEMBERS = new String("org.jooq.configuration.reflection-cache.get-annotated-members"); static final String DATA_REFLECTION_CACHE_GET_ANNOTATED_SETTERS = new String("org.jooq.configuration.reflection-cache.get-annotated-setters"); static final String DATA_REFLECTION_CACHE_GET_MATCHING_GETTER = new String("org.jooq.configuration.reflection-cache.get-matching-getter"); static final String DATA_REFLECTION_CACHE_GET_MATCHING_MEMBERS = new String("org.jooq.configuration.reflection-cache.get-matching-members"); static final String DATA_REFLECTION_CACHE_GET_MATCHING_SETTERS = new String("org.jooq.configuration.reflection-cache.get-matching-setters"); static final String DATA_REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS = new String("org.jooq.configuration.reflection-cache.has-column-annotations"); // ------------------------------------------------------------------------ // Other constants // ------------------------------------------------------------------------ /** * The default escape character for [a] LIKE [b] ESCAPE [...] * clauses. */ static final char ESCAPE = '!'; /** * Indicating whether JPA (javax.persistence) is on the * classpath. */ private static Boolean isJPAAvailable; /** * [#3696] The maximum number of consumed exceptions in * {@link #consumeExceptions(Configuration, PreparedStatement, SQLException)} * helps prevent infinite loops and {@link OutOfMemoryError}. */ private static int maxConsumedExceptions = 256; private static int maxConsumedResults = 65536; /** * A pattern for the dash line syntax */ private static final Pattern DASH_PATTERN = Pattern.compile("(-+)"); /** * A pattern for the dash line syntax */ private static final Pattern PLUS_PATTERN = Pattern.compile("\\+(-+)(?=\\+)"); /** * All characters that are matched by Java's interpretation of \s. *

* For a more accurate set of whitespaces, refer to * http://stackoverflow.com/a/4731164/521799. In the event of SQL * processing, it is probably safe to ignore most of those alternative * Unicode whitespaces. */ private static final String WHITESPACE = " \t\n\u000B\f\r"; /** * Acceptable prefixes for JDBC escape syntax. */ private static final String[] JDBC_ESCAPE_PREFIXES = { "{fn ", "{d ", "{t ", "{ts " }; // ------------------------------------------------------------------------ // XXX: Record constructors and related methods // ------------------------------------------------------------------------ /** * Turn a {@link Result} into a list of {@link Row} */ static final List rows(Result result) { List rows = new ArrayList(); for (Record record : result) rows.add(record.valuesRow()); return rows; } /** * Create a new record */ static final RecordDelegate newRecord(boolean fetched, Class type) { return newRecord(fetched, type, null); } /** * Create a new record */ static final RecordDelegate newRecord(boolean fetched, Class type, Field[] fields) { return newRecord(fetched, type, fields, null); } /** * Create a new record */ static final RecordDelegate newRecord(boolean fetched, Table type) { return newRecord(fetched, type, null); } /** * Create a new record */ @SuppressWarnings("unchecked") static final RecordDelegate newRecord(boolean fetched, Table type, Configuration configuration) { return (RecordDelegate) newRecord(fetched, type.getRecordType(), type.fields(), configuration); } /** * Create a new UDT record */ static final > RecordDelegate newRecord(boolean fetched, UDT type) { return newRecord(fetched, type, null); } /** * Create a new UDT record */ static final > RecordDelegate newRecord(boolean fetched, UDT type, Configuration configuration) { return newRecord(fetched, type.getRecordType(), type.fields(), configuration); } /** * Create a new record. */ static final RecordDelegate newRecord(boolean fetched, Class type, Field[] fields, Configuration configuration) { return newRecord(fetched, recordFactory(type, fields), configuration); } /** * Create a new record. */ static final RecordDelegate newRecord(boolean fetched, RecordFactory factory, Configuration configuration) { try { R record = factory.newInstance(); // [#3300] Records that were fetched from the database if (record instanceof AbstractRecord) ((AbstractRecord) record).fetched = fetched; return new RecordDelegate(configuration, record); } catch (Exception e) { throw new IllegalStateException("Could not construct new record", e); } } /** * Create a new record factory. */ @SuppressWarnings({ "unchecked", "rawtypes" }) static final RecordFactory recordFactory(final Class type, final Field[] fields) { // An ad-hoc type resulting from a JOIN or arbitrary SELECT if (type == RecordImpl.class || type == Record.class) { final RowImpl row = new RowImpl(fields); return new RecordFactory() { @Override public R newInstance() { return (R) new RecordImpl(row); } }; } // Any generated record else { try { // [#919] Allow for accessing non-public constructors final Constructor constructor = Reflect.accessible(type.getDeclaredConstructor()); return new RecordFactory() { @Override public R newInstance() { try { return constructor.newInstance(); } catch (Exception e) { throw new IllegalStateException("Could not construct new record", e); } } }; } catch (Exception e) { throw new IllegalStateException("Could not construct new record", e); } } } /** * [#2700] [#3582] If a POJO attribute is NULL, but the column is NOT NULL * then we should let the database apply DEFAULT values */ static final void resetChangedOnNotNull(Record record) { int size = record.size(); for (int i = 0; i < size; i++) if (record.get(i) == null) if (!record.field(i).getDataType().nullable()) record.changed(i, false); } /** * Extract the configuration from an attachable. */ static final Configuration getConfiguration(Attachable attachable) { if (attachable instanceof AttachableInternal) { return ((AttachableInternal) attachable).configuration(); } return null; } /** * Get an attachable's configuration or a new {@link DefaultConfiguration} * if null. */ static final Configuration configuration(Attachable attachable) { return configuration(attachable instanceof AttachableInternal ? ((AttachableInternal) attachable).configuration() : null); } /** * Get a configuration or a new {@link DefaultConfiguration} if * null. */ static final Configuration configuration(Configuration configuration) { return configuration != null ? configuration : new DefaultConfiguration(); } /** * Get a configuration's settings or default settings if the configuration * is null. */ static final Settings settings(Attachable attachable) { return configuration(attachable).settings(); } /** * Get a configuration's settings or default settings if the configuration * is null. */ static final Settings settings(Configuration configuration) { return configuration(configuration).settings(); } static final boolean attachRecords(Configuration configuration) { if (configuration != null) { Settings settings = configuration.settings(); if (settings != null) { return !FALSE.equals(settings.isAttachRecords()); } } return true; } static final Field[] fieldArray(Collection> fields) { return fields == null ? null : fields.toArray(new Field[0]); } // ------------------------------------------------------------------------ // XXX: Data-type related methods // ------------------------------------------------------------------------ /** * Useful conversion method */ static final Class[] types(Field[] fields) { return types(dataTypes(fields)); } /** * Useful conversion method */ static final Class[] types(DataType[] types) { if (types == null) return null; Class[] result = new Class[types.length]; for (int i = 0; i < types.length; i++) { if (types[i] != null) { result[i] = types[i].getType(); } else { result[i] = Object.class; } } return result; } /** * Useful conversion method */ static final Class[] types(Object[] values) { if (values == null) return null; Class[] result = new Class[values.length]; for (int i = 0; i < values.length; i++) { if (values[i] instanceof Field) { result[i] = ((Field) values[i]).getType(); } else if (values[i] != null) { result[i] = values[i].getClass(); } else { result[i] = Object.class; } } return result; } /** * Useful conversion method */ static final DataType[] dataTypes(Field[] fields) { if (fields == null) return null; DataType[] result = new DataType[fields.length]; for (int i = 0; i < fields.length; i++) { if (fields[i] != null) { result[i] = fields[i].getDataType(); } else { result[i] = getDataType(Object.class); } } return result; } /** * Useful conversion method */ static final DataType[] dataTypes(Class[] types) { if (types == null) return null; DataType[] result = new DataType[types.length]; for (int i = 0; i < types.length; i++) if (types[i] != null) result[i] = getDataType(types[i]); else result[i] = getDataType(Object.class); return result; } /** * Useful conversion method */ static final DataType[] dataTypes(Object[] values) { return dataTypes(types(values)); } // ------------------------------------------------------------------------ // XXX: General utility methods // ------------------------------------------------------------------------ static final String[] fieldNames(int length) { String[] result = new String[length]; for (int i = 0; i < length; i++) result[i] = "v" + i; return result; } static final String[] fieldNames(Field[] fields) { if (fields == null) return null; String[] result = new String[fields.length]; for (int i = 0; i < fields.length; i++) result[i] = fields[i].getName(); return result; } static final Field[] fields(int length) { Field[] result = new Field[length]; String[] names = fieldNames(length); for (int i = 0; i < length; i++) result[i] = DSL.field(name(names[i])); return result; } static final Field[] aliasedFields(Field[] fields, String[] aliases) { if (fields == null) return null; Field[] result = new Field[fields.length]; for (int i = 0; i < fields.length; i++) result[i] = fields[i].as(aliases[i]); return result; } static final Field[] fieldsByName(String[] fieldNames) { return fieldsByName(null, fieldNames); } static final Field[] fieldsByName(String tableName, String[] fieldNames) { if (fieldNames == null) return null; Field[] result = new Field[fieldNames.length]; if (tableName == null) for (int i = 0; i < fieldNames.length; i++) result[i] = DSL.field(name(fieldNames[i])); else for (int i = 0; i < fieldNames.length; i++) result[i] = DSL.field(name(tableName, fieldNames[i])); return result; } static final Field[] fieldsByName(Name[] names) { if (names == null) return null; Field[] result = new Field[names.length]; for (int i = 0; i < names.length; i++) result[i] = DSL.field(names[i]); return result; } static final Name[] names(String[] names) { if (names == null) return null; Name[] result = new Name[names.length]; for (int i = 0; i < names.length; i++) result[i] = DSL.name(names[i]); return result; } private static final IllegalArgumentException fieldExpected(Object value) { return new IllegalArgumentException("Cannot interpret argument of type " + value.getClass() + " as a Field: " + value); } /** * Be sure that a given object is a field. * * @param value The argument object * @return The argument object itself, if it is a {@link Field}, or a bind * value created from the argument object. */ @SuppressWarnings("unchecked") static final Field field(T value) { // Fields can be mixed with constant values if (value instanceof Field) { return (Field) value; } // [#4771] Any other QueryPart type is not supported here else if (value instanceof QueryPart) { throw fieldExpected(value); } else { return val(value); } } // The following overloads help performance by avoiding runtime data type lookups // ------------------------------------------------------------------------------ static final Param field(byte value) { return val((Object) value, SQLDataType.TINYINT); } static final Param field(Byte value) { return val((Object) value, SQLDataType.TINYINT); } static final Param field(UByte value) { return val((Object) value, SQLDataType.TINYINTUNSIGNED); } static final Param field(short value) { return val((Object) value, SQLDataType.SMALLINT); } static final Param field(Short value) { return val((Object) value, SQLDataType.SMALLINT); } static final Param field(UShort value) { return val((Object) value, SQLDataType.SMALLINTUNSIGNED); } static final Param field(int value) { return val((Object) value, SQLDataType.INTEGER); } static final Param field(Integer value) { return val((Object) value, SQLDataType.INTEGER); } static final Param field(UInteger value) { return val((Object) value, SQLDataType.INTEGERUNSIGNED); } static final Param field(long value) { return val((Object) value, SQLDataType.BIGINT); } static final Param field(Long value) { return val((Object) value, SQLDataType.BIGINT); } static final Param field(ULong value) { return val((Object) value, SQLDataType.BIGINTUNSIGNED); } static final Param field(float value) { return val((Object) value, SQLDataType.REAL); } static final Param field(Float value) { return val((Object) value, SQLDataType.REAL); } static final Param field(double value) { return val((Object) value, SQLDataType.DOUBLE); } static final Param field(Double value) { return val((Object) value, SQLDataType.DOUBLE); } static final Param field(boolean value) { return val((Object) value, SQLDataType.BOOLEAN); } static final Param field(Boolean value) { return val((Object) value, SQLDataType.BOOLEAN); } static final Param field(BigDecimal value) { return val((Object) value, SQLDataType.DECIMAL); } static final Param field(BigInteger value) { return val((Object) value, SQLDataType.DECIMAL_INTEGER); } static final Param field(byte[] value) { return val((Object) value, SQLDataType.VARBINARY); } static final Param field(String value) { return val((Object) value, SQLDataType.VARCHAR); } static final Param field(Date value) { return val((Object) value, SQLDataType.DATE); } static final Param