/*
* 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_SCHEMA;
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.keyword;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.nullSafe;
import static org.jooq.impl.DSL.select;
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.impl.Keywords.K_AS;
import static org.jooq.impl.Keywords.K_AUTOINCREMENT;
import static org.jooq.impl.Keywords.K_AUTO_INCREMENT;
import static org.jooq.impl.Keywords.K_BEGIN;
import static org.jooq.impl.Keywords.K_BEGIN_CATCH;
import static org.jooq.impl.Keywords.K_BEGIN_TRY;
import static org.jooq.impl.Keywords.K_DECLARE;
import static org.jooq.impl.Keywords.K_DO;
import static org.jooq.impl.Keywords.K_ELSE;
import static org.jooq.impl.Keywords.K_END;
import static org.jooq.impl.Keywords.K_END_CATCH;
import static org.jooq.impl.Keywords.K_END_IF;
import static org.jooq.impl.Keywords.K_END_LOOP;
import static org.jooq.impl.Keywords.K_END_TRY;
import static org.jooq.impl.Keywords.K_EXCEPTION;
import static org.jooq.impl.Keywords.K_EXEC;
import static org.jooq.impl.Keywords.K_EXECUTE_BLOCK;
import static org.jooq.impl.Keywords.K_EXECUTE_IMMEDIATE;
import static org.jooq.impl.Keywords.K_EXECUTE_STATEMENT;
import static org.jooq.impl.Keywords.K_FOR;
import static org.jooq.impl.Keywords.K_GENERATED_BY_DEFAULT_AS_IDENTITY;
import static org.jooq.impl.Keywords.K_IDENTITY;
import static org.jooq.impl.Keywords.K_IF;
import static org.jooq.impl.Keywords.K_IN;
import static org.jooq.impl.Keywords.K_INT;
import static org.jooq.impl.Keywords.K_LIKE;
import static org.jooq.impl.Keywords.K_LOOP;
import static org.jooq.impl.Keywords.K_NULL;
import static org.jooq.impl.Keywords.K_NVARCHAR;
import static org.jooq.impl.Keywords.K_RAISE;
import static org.jooq.impl.Keywords.K_RAISERROR;
import static org.jooq.impl.Keywords.K_SERIAL;
import static org.jooq.impl.Keywords.K_SERIAL8;
import static org.jooq.impl.Keywords.K_START_WITH;
import static org.jooq.impl.Keywords.K_THEN;
import static org.jooq.impl.Keywords.K_THROW;
import static org.jooq.impl.Keywords.K_WHEN;
import static org.jooq.tools.reflect.Reflect.accessible;
import java.io.Serializable;
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.sql.Types;
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.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.CommonTableExpression;
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.Query;
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.RowN;
import org.jooq.SQLDialect;
import org.jooq.Schema;
import org.jooq.Select;
import org.jooq.SelectField;
import org.jooq.Sequence;
import org.jooq.SortField;
import org.jooq.Table;
import org.jooq.TableRecord;
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);
// ------------------------------------------------------------------------
// Empty arrays for use with Collection.toArray()
// ------------------------------------------------------------------------
static final Class<?>[] EMPTY_CLASS = {};
static final Clause[] EMPTY_CLAUSE = {};
static final Collection<?>[] EMPTY_COLLECTION = {};
static final ExecuteListener[] EMPTY_EXECUTE_LISTENER = {};
static final Field<?>[] EMPTY_FIELD = {};
static final int[] EMPTY_INT = {};
static final byte[] EMPTY_BYTE = {};
static final Param<?>[] EMPTY_PARAM = {};
static final Query[] EMPTY_QUERY = {};
static final QueryPart[] EMPTY_QUERYPART = {};
static final Record[] EMPTY_RECORD = {};
static final RowN[] EMPTY_ROWN = {};
static final CommonTableExpression<?>[] EMPTY_COMMON_TABLE_EXPRESSION = {};
static final String[] EMPTY_STRING = {};
static final Name[] EMPTY_NAME = {};
static final TableRecord<?>[] EMPTY_TABLE_RECORD = {};
static final UpdatableRecord<?>[] EMPTY_UPDATABLE_RECORD = {};
// ------------------------------------------------------------------------
// 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.
* <p>
* 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
* <code>FOR UPDATE</code> 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.
* <p>
* 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:
* <ul>
* <li>{@link SQLDialect#ASE} : 2000</li>
* <li>{@link SQLDialect#INGRES} : 1024</li>
* <li>{@link SQLDialect#SQLITE} : 999</li>
* <li>{@link SQLDialect#SQLSERVER} : 2100</li>
* </ul>
*/
DATA_FORCE_STATIC_STATEMENT,
/**
* [#2665] Omit the emission of clause events by {@link QueryPart}s.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* The window definitions declared in the <code>WINDOW</code> clause are
* needed in the <code>SELECT</code> 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
* <code>ORDER BY</code> 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
* <code>ORDER BY</code> 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 <code>INSERT INTO t SELECT</code> statement. Without any
* explicit column list, the <code>SELECT</code> 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.
* <p>
* <code>new String()</code> 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 <code>[a] LIKE [b] ESCAPE [...]</code>
* clauses.
*/
static final char ESCAPE = '!';
/**
* Indicating whether JPA (<code>javax.persistence</code>) 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.
* <p>
* 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 "
};
/**
* "Suffixes" that are placed behind a "?" character to form an operator,
* rather than a JDBC bind variable. This is particularly useful to prevent
* parsing PostgreSQL operators as bind variables, as can be seen here:
* <a href=
* "https://www.postgresql.org/docs/9.5/static/functions-json.html">https://www.postgresql.org/docs/9.5/static/functions-json.html</a>
* <p>
* Known PostgreSQL JSON operators:
* <ul>
* <li>?|</li>
* <li>?&</li>
* </ul>
*/
private static final String[] NON_BIND_VARIABLE_SUFFIXES = {
"?",
"|",
"&"
};
// ------------------------------------------------------------------------
// XXX: Record constructors and related methods
// ------------------------------------------------------------------------
/**
* Turn a {@link Result} into a list of {@link Row}
*/
static final List<Row> rows(Result<?> result) {
List<Row> rows = new ArrayList<Row>();
for (Record record : result)
rows.add(record.valuesRow());
return rows;
}
/**
* Create a new record
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Class<R> type) {
return newRecord(fetched, type, null);
}
/**
* Create a new record
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Class<R> type, Field<?>[] fields) {
return newRecord(fetched, type, fields, null);
}
/**
* Create a new record
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Table<R> type) {
return newRecord(fetched, type, null);
}
/**
* Create a new record
*/
@SuppressWarnings("unchecked")
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Table<R> type, Configuration configuration) {
return (RecordDelegate<R>) newRecord(fetched, type.getRecordType(), type.fields(), configuration);
}
/**
* Create a new UDT record
*/
static final <R extends UDTRecord<R>> RecordDelegate<R> newRecord(boolean fetched, UDT<R> type) {
return newRecord(fetched, type, null);
}
/**
* Create a new UDT record
*/
static final <R extends UDTRecord<R>> RecordDelegate<R> newRecord(boolean fetched, UDT<R> type, Configuration configuration) {
return newRecord(fetched, type.getRecordType(), type.fields(), configuration);
}
/**
* Create a new record.
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Class<R> type, Field<?>[] fields, Configuration configuration) {
return newRecord(fetched, recordFactory(type, fields), configuration);
}
/**
* Create a new record.
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, RecordFactory<R> 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<R>(configuration, record);
}
catch (Exception e) {
throw new IllegalStateException("Could not construct new record", e);
}
}
/**
* Create a new record factory.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
static final <R extends Record> RecordFactory<R> recordFactory(final Class<R> 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<R>() {
@Override
public R newInstance() {
return (R) new RecordImpl(row);
}
};
}
// Any generated record
else {
try {
// [#919] Allow for accessing non-public constructors
final Constructor<R> constructor = Reflect.accessible(type.getDeclaredConstructor());
return new RecordFactory<R>() {
@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 <code>null</code>.
*/
static final Configuration configuration(Attachable attachable) {
return configuration(attachable instanceof AttachableInternal
? ((AttachableInternal) attachable).configuration()
: null);
}
/**
* Get a configuration or a new {@link DefaultConfiguration} if
* <code>null</code>.
*/
static final Configuration configuration(Configuration configuration) {
return configuration != null ? configuration : new DefaultConfiguration();
}
/**
* Get a configuration's settings or default settings if the configuration
* is <code>null</code>.
*/
static final Settings settings(Attachable attachable) {
return configuration(attachable).settings();
}
/**
* Get a configuration's settings or default settings if the configuration
* is <code>null</code>.
*/
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<? extends Field<?>> fields) {
return fields == null ? null : fields.toArray(EMPTY_FIELD);
}
// ------------------------------------------------------------------------
// 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 SortField<?>[] sortFields(Field<?>[] fields) {
if (fields == null)
return null;
SortField<?>[] result = new SortField[fields.length];
for (int i = 0; i < fields.length; i++)
result[i] = fields[i].asc();
return result;
}
static final Name[] fieldNames(int length) {
Name[] result = new Name[length];
for (int i = 0; i < length; i++)
result[i] = name("v" + i);
return result;
}
static final String[] fieldNameStrings(int length) {
String[] result = new String[length];
for (int i = 0; i < length; i++)
result[i] = "v" + i;
return result;
}
static final Name[] fieldNames(Field<?>[] fields) {
if (fields == null)
return null;
Name[] result = new Name[fields.length];
for (int i = 0; i < fields.length; i++)
result[i] = fields[i].getUnqualifiedName();
return result;
}
static final Field<?>[] fields(int length) {
Field<?>[] result = new Field[length];
Name[] 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, Name[] 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(Collection<String> fieldNames) {
return fieldsByName(null, fieldNames.toArray(EMPTY_STRING));
}
static final Field<?>[] fieldsByName(String[] fieldNames) {
return fieldsByName(null, fieldNames);
}
static final Field<?>[] fieldsByName(String tableName, Collection<String> fieldNames) {
return fieldsByName(tableName, fieldNames.toArray(EMPTY_STRING));
}
static final Field<?>[] fieldsByName(Name tableName, Name[] 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(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(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 <T> Field<T> field(T value) {
// Fields can be mixed with constant values
if (value instanceof Field<?>) {
return (Field<T>) 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<Byte> field(byte value) {
return val((Object) value, SQLDataType.TINYINT);
}
static final Param<Byte> field(Byte value) {
return val((Object) value, SQLDataType.TINYINT);
}
static final Param<UByte> field(UByte value) {
return val((Object) value, SQLDataType.TINYINTUNSIGNED);
}
static final Param<Short> field(short value) {
return val((Object) value, SQLDataType.SMALLINT);
}
static final Param<Short> field(Short value) {
return val((Object) value, SQLDataType.SMALLINT);
}
static final Param<UShort> field(UShort value) {
return val((Object) value, SQLDataType.SMALLINTUNSIGNED);
}
static final Param<Integer> field(int value) {
return val((Object) value, SQLDataType.INTEGER);
}
static final Param<Integer> field(Integer value) {
return val((Object) value, SQLDataType.INTEGER);
}
static final Param<UInteger> field(UInteger value) {
return val((Object) value, SQLDataType.INTEGERUNSIGNED);
}
static final Param<Long> field(long value) {
return val((Object) value, SQLDataType.BIGINT);
}
static final Param<Long> field(Long value) {
return val((Object) value, SQLDataType.BIGINT);
}
static final Param<ULong> field(ULong value) {
return val((Object) value, SQLDataType.BIGINTUNSIGNED);
}
static final Param<Float> field(float value) {
return val((Object) value, SQLDataType.REAL);
}
static final Param<Float> field(Float value) {
return val((Object) value, SQLDataType.REAL);
}
static final Param<Double> field(double value) {
return val((Object) value, SQLDataType.DOUBLE);
}
static final Param<Double> field(Double value) {
return val((Object) value, SQLDataType.DOUBLE);
}
static final Param<Boolean> field(boolean value) {
return val((Object) value, SQLDataType.BOOLEAN);
}
static final Param<Boolean> field(Boolean value) {
return val((Object) value, SQLDataType.BOOLEAN);
}
static final Param<BigDecimal> field(BigDecimal value) {
return val((Object) value, SQLDataType.DECIMAL);
}
static final Param<BigInteger> field(BigInteger value) {
return val((Object) value, SQLDataType.DECIMAL_INTEGER);
}
static final Param<byte[]> field(byte[] value) {
return val((Object) value, SQLDataType.VARBINARY);
}
static final Param<String> field(String value) {
return val((Object) value, SQLDataType.VARCHAR);
}
static final Param<Date> field(Date value) {
return val((Object) value, SQLDataType.DATE);
}
static final Param<Time> field(Time value) {
return val((Object) value, SQLDataType.TIME);
}
static final Param<Timestamp> field(Timestamp value) {
return val((Object) value, SQLDataType.TIMESTAMP);
}
static final Param<LocalDate> field(LocalDate value) {
return val((Object) value, SQLDataType.LOCALDATE);
}
static final Param<LocalTime> field(LocalTime value) {
return val((Object) value, SQLDataType.LOCALTIME);
}
static final Param<LocalDateTime> field(LocalDateTime value) {
return val((Object) value, SQLDataType.LOCALDATETIME);
}
static final Param<OffsetTime> field(OffsetTime value) {
return val((Object) value, SQLDataType.OFFSETTIME);
}
static final Param<OffsetDateTime> field(OffsetDateTime value) {
return val((Object) value, SQLDataType.OFFSETDATETIME);
}
static final Param<UUID> field(UUID value) {
return val((Object) value, SQLDataType.UUID);
}
/**
* @deprecated - This method is probably called by mistake (ambiguous static import).
*/
@Deprecated
static final Field<Object> field(Name name) {
return DSL.field(name);
}
/**
* Be sure that a given object is a field.
*
* @param value The argument object
* @param field The field to take the bind value type from
* @return The argument object itself, if it is a {@link Field}, or a bind
* value created from the argument object.
*/
@SuppressWarnings("unchecked")
static final <T> Field<T> field(Object value, Field<T> field) {
// Fields can be mixed with constant values
if (value instanceof Field<?>) {
return (Field<T>) value;
}
// [#4771] Any other QueryPart type is not supported here
else if (value instanceof QueryPart) {
throw fieldExpected(value);
}
else {
return val(value, field);
}
}
/**
* Be sure that a given object is a field.
*
* @param value The argument object
* @param type The type to take the bind value type from
* @return The argument object itself, if it is a {@link Field}, or a bind
* value created from the argument object.
*/
@SuppressWarnings("unchecked")
static final <T> Field<T> field(Object value, Class<T> type) {
// Fields can be mixed with constant values
if (value instanceof Field<?>) {
return (Field<T>) value;
}
// [#4771] Any other QueryPart type is not supported here
else if (value instanceof QueryPart) {
throw fieldExpected(value);
}
else {
return val(value, type);
}
}
/**
* Be sure that a given object is a field.
*
* @param value The argument object
* @param type The type to take the bind value type from
* @return The argument object itself, if it is a {@link Field}, or a bind
* value created from the argument object.
*/
@SuppressWarnings("unchecked")
static final <T> Field<T> field(Object value, DataType<T> type) {
// Fields can be mixed with constant values
if (value instanceof Field<?>) {
return (Field<T>) value;
}
// [#4771] Any other QueryPart type is not supported here
else if (value instanceof QueryPart) {
throw fieldExpected(value);
}
else {
return val(value, type);
}
}
/**
* Be sure that a given set of objects are fields.
*
* @param values The argument objects
* @return The argument objects themselves, if they are {@link Field}s, or a bind
* values created from the argument objects.
*/
static final <T> List<Field<T>> fields(T[] values) {
List<Field<T>> result = new ArrayList<Field<T>>();
if (values != null)
for (T value : values)
result.add(field(value));
return result;
}
/**
* Be sure that a given set of objects are fields.
*
* @param values The argument objects
* @param field The field to take the bind value types from
* @return The argument objects themselves, if they are {@link Field}s, or a bind
* values created from the argument objects.
*/
static final List<Field<?>> fields(Object[] values, Field<?> field) {
List<Field<?>> result = new ArrayList<Field<?>>();
if (values != null && field != null) {
for (int i = 0; i < values.length; i++) {
result.add(field(values[i], field));
}
}
return result;
}
/**
* Be sure that a given set of objects are fields.
*
* @param values The argument objects
* @param fields The fields to take the bind value types from
* @return The argument objects themselves, if they are {@link Field}s, or a bind
* values created from the argument objects.
*/
static final List<Field<?>> fields(Object[] values, Field<?>[] fields) {
List<Field<?>> result = new ArrayList<Field<?>>();
if (values != null && fields != null) {
for (int i = 0; i < values.length && i < fields.length; i++) {
result.add(field(values[i], fields[i]));
}
}
return result;
}
/**
* Be sure that a given set of objects are fields.
*
* @param values The argument objects
* @param type The type to take the bind value types from
* @return The argument objects themselves, if they are {@link Field}s, or a bind
* values created from the argument objects.
*/
static final List<Field<?>> fields(Object[] values, Class<?> type) {
List<Field<?>> result = new ArrayList<Field<?>>();
if (values != null && type != null) {
for (int i = 0; i < values.length; i++) {
result.add(field(values[i], type));
}
}
return result;
}
/**
* Be sure that a given set of objects are fields.
*
* @param values The argument objects
* @param types The types to take the bind value types from
* @return The argument objects themselves, if they are {@link Field}s, or a bind
* values created from the argument objects.
*/
static final List<Field<?>> fields(Object[] values, Class<?>[] types) {
List<Field<?>> result = new ArrayList<Field<?>>();
if (values != null && types != null) {
for (int i = 0; i < values.length && i < types.length; i++) {
result.add(field(values[i], types[i]));
}
}
return result;
}
/**
* Be sure that a given set of objects are fields.
*
* @param values The argument objects
* @param type The type to take the bind value types from
* @return The argument objects themselves, if they are {@link Field}s, or a bind
* values created from the argument objects.
*/
static final List<Field<?>> fields(Object[] values, DataType<?> type) {
List<Field<?>> result = new ArrayList<Field<?>>();
if (values != null && type != null) {
for (int i = 0; i < values.length; i++) {
result.add(field(values[i], type));
}
}
return result;
}
/**
* Be sure that a given set of objects are fields.
*
* @param values The argument objects
* @param types The types to take the bind value types from
* @return The argument objects themselves, if they are {@link Field}s, or a bind
* values created from the argument objects.
*/
static final List<Field<?>> fields(Object[] values, DataType<?>[] types) {
List<Field<?>> result = new ArrayList<Field<?>>();
if (values != null && types != null) {
for (int i = 0; i < values.length && i < types.length; i++) {
result.add(field(values[i], types[i]));
}
}
return result;
}
static final List<Field<?>> fields(Collection<? extends SelectField<?>> fields) {
List<Field<?>> result = new ArrayList<Field<?>>();
if (fields != null) {
for (SelectField<?> field : fields) {
result.add(DSL.field(field));
}
}
return result;
}
static final List<Field<?>> fields(SelectField<?>... fields) {
return fields == null ? fields(Collections.<SelectField<?>>emptyList()) : fields(Arrays.asList(fields));
}
static final <T> List<Field<T>> inline(T[] values) {
List<Field<T>> result = new ArrayList<Field<T>>();
if (values != null) {
for (T value : values) {
result.add(DSL.inline(value));
}
}
return result;
}
/**
* Return a list of unqualified {@link Field}s.
*/
static final List<Field<?>> unqualify(List<? extends Field<?>> fields) {
QueryPartList<Field<?>> result = new QueryPartList<Field<?>>();
for (Field<?> field : fields)
result.add(DSL.field(name(field.getName())));
return result;
}
/**
* A utility method that fails with an exception if
* {@link Row#indexOf(Field)} doesn't return any index.
*/
static final int indexOrFail(Row row, Field<?> field) {
int result = row.indexOf(field);
if (result < 0)
throw new IllegalArgumentException("Field (" + field + ") is not contained in Row " + row);
return result;
}
/**
* A utility method that fails with an exception if
* {@link Row#indexOf(String)} doesn't return any index.
*/
static final int indexOrFail(Row row, String fieldName) {
int result = row.indexOf(fieldName);
if (result < 0)
throw new IllegalArgumentException("Field (" + fieldName + ") is not contained in Row " + row);
return result;
}
/**
* A utility method that fails with an exception if
* {@link Row#indexOf(Name)} doesn't return any index.
*/
static final int indexOrFail(Row row, Name fieldName) {
int result = row.indexOf(fieldName);
if (result < 0)
throw new IllegalArgumentException("Field (" + fieldName + ") is not contained in Row " + row);
return result;
}
/**
* A utility method that fails with an exception if
* {@link RecordType#indexOf(Field)} doesn't return any index.
*/
static final int indexOrFail(RecordType<?> row, Field<?> field) {
int result = row.indexOf(field);
if (result < 0)
throw new IllegalArgumentException("Field (" + field + ") is not contained in RecordType " + row);
return result;
}
/**
* A utility method that fails with an exception if
* {@link RecordType#indexOf(String)} doesn't return any index.
*/
static final int indexOrFail(RecordType<?> row, String fieldName) {
int result = row.indexOf(fieldName);
if (result < 0)
throw new IllegalArgumentException("Field (" + fieldName + ") is not contained in RecordType " + row);
return result;
}
/**
* A utility method that fails with an exception if
* {@link RecordType#indexOf(Name)} doesn't return any index.
*/
static final int indexOrFail(RecordType<?> row, Name fieldName) {
int result = row.indexOf(fieldName);
if (result < 0)
throw new IllegalArgumentException("Field (" + fieldName + ") is not contained in RecordType " + row);
return result;
}
/**
* Create a new array
*/
@SafeVarargs
static final <T> T[] array(T... array) {
return array;
}
/**
* Use this rather than {@link Arrays#asList(Object...)} for
* <code>null</code>-safety
*/
@SafeVarargs
static final <T> List<T> list(T... array) {
return array == null ? Collections.<T>emptyList() : Arrays.asList(array);
}
/**
* Turn a {@link Record} into a {@link Map}
*/
static final Map<Field<?>, Object> mapOfChangedValues(Record record) {
Map<Field<?>, Object> result = new LinkedHashMap<Field<?>, Object>();
int size = record.size();
for (int i = 0; i < size; i++)
if (record.changed(i))
result.put(record.field(i), record.get(i));
return result;
}
/**
* Extract the first item from an iterable or <code>null</code>, if there is
* no such item, or if iterable itself is <code>null</code>
*/
static final <T> T first(Iterable<? extends T> iterable) {
if (iterable == null) {
return null;
}
else {
Iterator<? extends T> iterator = iterable.iterator();
if (iterator.hasNext()) {
return iterator.next();
}
else {
return null;
}
}
}
/**
* Get the only element from a list or <code>null</code>, or throw an
* exception
*
* @param list The list
* @return The only element from the list or <code>null</code>
* @throws TooManyRowsException Thrown if the list contains more than one
* element
*/
static final <R extends Record> R filterOne(List<R> list) throws TooManyRowsException {
int size = list.size();
if (size == 1) {
return list.get(0);
}
else if (size > 1) {
throw new TooManyRowsException("Too many rows selected : " + size);
}
return null;
}
/**
* Get the only element from a cursor or <code>null</code>, or throw an
* exception.
* <p>
* [#2373] This method will always close the argument cursor, as it is
* supposed to be completely consumed by this method.
*
* @param cursor The cursor
* @return The only element from the cursor or <code>null</code>
* @throws TooManyRowsException Thrown if the cursor returns more than one
* element
*/
static final <R extends Record> R fetchOne(Cursor<R> cursor) throws TooManyRowsException {
try {
R record = cursor.fetchOne();
if (cursor.hasNext()) {
throw new TooManyRowsException("Cursor returned more than one result");
}
return record;
}
finally {
cursor.close();
}
}
/**
* Visit each query part from a collection, given a context.
*/
static final <C extends Context<? super C>> C visitAll(C ctx, Collection<? extends QueryPart> parts) {
if (parts != null) {
for (QueryPart part : parts) {
ctx.visit(part);
}
}
return ctx;
}
/**
* Visit each query part from an array, given a context.
*/
static final <C extends Context<? super C>> C visitAll(C ctx, QueryPart[] parts) {
if (parts != null) {
for (QueryPart part : parts) {
ctx.visit(part);
}
}
return ctx;
}
/**
* Render and bind a list of {@link QueryPart} to plain SQL
* <p>
* This will perform two actions:
* <ul>
* <li>When {@link RenderContext} is provided, it will render plain SQL to
* the context, substituting {numbered placeholders} and bind values if
* {@link RenderContext#inline()} is set</li>
* <li>When {@link BindContext} is provided, it will bind the list of
* {@link QueryPart} according to the {numbered placeholders} and bind
* values in the sql string</li>
* </ul>
*/
@SuppressWarnings("null")
static final void renderAndBind(Context<?> ctx, String sql, List<QueryPart> substitutes) {
RenderContext render = (RenderContext) ((ctx instanceof RenderContext) ? ctx : null);
BindContext bind = (BindContext) ((ctx instanceof BindContext) ? ctx : null);
int substituteIndex = 0;
char[] sqlChars = sql.toCharArray();
// [#1593] Create a dummy renderer if we're in bind mode
if (render == null) render = new DefaultRenderContext(bind.configuration());
SQLDialect dialect = render.configuration().dialect();
SQLDialect family = dialect.family();
boolean mysql = asList(MARIADB, MYSQL).contains(family);
String[][] quotes = QUOTES.get(family);
// [#3630] Depending on this setting, we need to consider backslashes as escape characters within string literals.
boolean needsBackslashEscaping = needsBackslashEscaping(ctx.configuration());
characterLoop:
for (int i = 0; i < sqlChars.length; i++) {
// [#1797] Skip content inside of single-line comments, e.g.
// select 1 x -- what's this ?'?
// from t_book -- what's that ?'?
// where id = ?
if (peek(sqlChars, i, "--") ||
// [#4182] MySQL also supports # as a comment character, and requires
// -- to be followed by a whitespace, although the latter is also not
// handled correctly by the MySQL JDBC driver (yet). See
// http://bugs.mysql.com/bug.php?id=76623
(mysql && peek(sqlChars, i, "#"))) {
// Consume the complete comment
for (; i < sqlChars.length && sqlChars[i] != '\r' && sqlChars[i] != '\n'; render.sql(sqlChars[i++]));
// Consume the newline character
if (i < sqlChars.length) render.sql(sqlChars[i]);
}
// [#1797] Skip content inside of multi-line comments, e.g.
// select 1 x /* what's this ?'?
// I don't know ?'? */
// from t_book where id = ?
else if (peek(sqlChars, i, "/*")) {
// Consume the complete comment
for (; !peek(sqlChars, i, "*/"); render.sql(sqlChars[i++]));
// Consume the comment delimiter
render.sql(sqlChars[i++]);
render.sql(sqlChars[i]);
}
// [#1031] [#1032] Skip ? inside of string literals, e.g.
// insert into x values ('Hello? Anybody out there?');
else if (sqlChars[i] == '\'') {
// Consume the initial string literal delimiter
render.sql(sqlChars[i++]);
// Consume the whole string literal
for (;;) {
// [#3000] [#3630] Consume backslash-escaped characters if needed
if (sqlChars[i] == '\\' && needsBackslashEscaping) {
render.sql(sqlChars[i++]);
}
// Consume an escaped apostrophe
else if (peek(sqlChars, i, "''")) {
render.sql(sqlChars[i++]);
}
// Break on the terminal string literal delimiter
else if (peek(sqlChars, i, "'")) {
break;
}
// Consume string literal content
render.sql(sqlChars[i++]);
}
// Consume the terminal string literal delimiter
render.sql(sqlChars[i]);
}
// [#3297] Skip ? inside of quoted identifiers, e.g.
// update x set v = "Column Name with a ? (question mark)"
else if (peekAny(sqlChars, i, quotes[QUOTE_START_DELIMITER])) {
// Main identifier delimiter or alternative one?
int delimiter = 0;
for (int d = 0; d < quotes[QUOTE_START_DELIMITER].length; d++) {
if (peek(sqlChars, i, quotes[QUOTE_START_DELIMITER][d])) {
delimiter = d;
break;
}
}
// Consume the initial identifier delimiter
for (int d = 0; d < quotes[QUOTE_START_DELIMITER][delimiter].length(); d++)
render.sql(sqlChars[i++]);
// Consume the whole identifier
for (;;) {
// Consume an escaped quote
if (peek(sqlChars, i, quotes[QUOTE_END_DELIMITER_ESCAPED][delimiter])) {
for (int d = 0; d < quotes[QUOTE_END_DELIMITER_ESCAPED][delimiter].length(); d++)
render.sql(sqlChars[i++]);
}
// Break on the terminal identifier delimiter
else if (peek(sqlChars, i, quotes[QUOTE_END_DELIMITER][delimiter])) {
break;
}
// Consume identifier content
render.sql(sqlChars[i++]);
}
// Consume the terminal identifier delimiter
for (int d = 0; d < quotes[QUOTE_END_DELIMITER][delimiter].length(); d++) {
if (d > 0)
i++;
render.sql(sqlChars[i]);
}
}
// Inline bind variables only outside of string literals
else if (substituteIndex < substitutes.size() &&
((sqlChars[i] == '?')
// [#4131] Named bind variables of the form :identifier
// Watch out for the PostgreSQL cast operator ::
|| (sqlChars[i] == ':'
&& i + 1 < sqlChars.length && isJavaIdentifierPart(sqlChars[i + 1])
&&(i - 1 < 0 || sqlChars[i - 1] != ':')))) {
// [#5307] Consume PostgreSQL style operators. These aren't bind variables!
if (sqlChars[i] == '?' && i + 1 < sqlChars.length) {
for (String suffix : NON_BIND_VARIABLE_SUFFIXES) {
if (peek(sqlChars, i + 1, suffix)) {
for (int j = i; i - j <= suffix.length(); i++)
render.sql(sqlChars[i]);
render.sql(sqlChars[i]);
continue characterLoop;
}
}
}
// [#4131] Consume the named bind variable
if (sqlChars[i] == ':')
while (++i < sqlChars.length && isJavaIdentifierPart(sqlChars[i]));
QueryPart substitute = substitutes.get(substituteIndex++);
if (render.paramType() == INLINED || render.paramType() == NAMED || render.paramType() == NAMED_OR_INLINED) {
render.visit(substitute);
}
else {
CastMode previous = render.castMode();
render.castMode(CastMode.NEVER)
.visit(substitute)
.castMode(previous);
}
if (bind != null) {
bind.visit(substitute);
}
}
// [#1432] Inline substitues for {numbered placeholders} outside of string literals
else if (sqlChars[i] == '{') {
// [#1461] Be careful not to match any JDBC escape syntax
if (peekAny(sqlChars, i, JDBC_ESCAPE_PREFIXES, true)) {
render.sql(sqlChars[i]);
}
// Consume the whole token
else {
int start = ++i;
for (; i < sqlChars.length && sqlChars[i] != '}'; i++);
int end = i;
String token = sql.substring(start, end);
// Try getting the {numbered placeholder}
try {
QueryPart substitute = substitutes.get(Integer.valueOf(token));
render.visit(substitute);
if (bind != null) {
bind.visit(substitute);
}
}
// If the above failed, then we're dealing with a {keyword}
catch (NumberFormatException e) {
render.visit(DSL.keyword(token));
}
}
}
// Any other character
else {
render.sql(sqlChars[i]);
}
}
}
/**
* Whether backslash escaping is needed in inlined string literals.
*/
static final boolean needsBackslashEscaping(Configuration configuration) {
BackslashEscaping escaping = getBackslashEscaping(configuration.settings());
return escaping == ON || (escaping == DEFAULT && EnumSet.of(MARIADB, MYSQL).contains(configuration.dialect().family()));
}
/**
* Peek for a string at a given <code>index</code> of a <code>char[]</code>
*
* @param sqlChars The char array to peek into
* @param index The index within the char array to peek for a string
* @param peek The string to peek for
*/
static final boolean peek(char[] sqlChars, int index, String peek) {
return peek(sqlChars, index, peek, false);
}
/**
* Peek for a string at a given <code>index</code> of a <code>char[]</code>
*
* @param sqlChars The char array to peek into
* @param index The index within the char array to peek for a string
* @param peek The string to peek for
* @param anyWhitespace A whitespace character in <code>peekAny</code>
* represents "any" whitespace character as defined in
* {@link #WHITESPACE}, or in Java Regex "\s".
*/
static final boolean peek(char[] sqlChars, int index, String peek, boolean anyWhitespace) {
char[] peekArray = peek.toCharArray();
peekArrayLoop:
for (int i = 0; i < peekArray.length; i++) {
if (index + i >= sqlChars.length) {
return false;
}
if (sqlChars[index + i] != peekArray[i]) {
// [#3430] In some cases, we don't care about the type of whitespace.
if (anyWhitespace && peekArray[i] == ' ') {
for (int j = 0; j < WHITESPACE.length(); j++) {
if (sqlChars[index + i] == WHITESPACE.charAt(j)) {
continue peekArrayLoop;
}
}
}
return false;
}
}
return true;
}
/**
* Peek for several strings at a given <code>index</code> of a <code>char[]</code>
*
* @param sqlChars The char array to peek into
* @param index The index within the char array to peek for a string
* @param peekAny The strings to peek for
*/
static final boolean peekAny(char[] sqlChars, int index, String[] peekAny) {
return peekAny(sqlChars, index, peekAny, false);
}
/**
* Peek for several strings at a given <code>index</code> of a
* <code>char[]</code>
*
* @param sqlChars The char array to peek into
* @param index The index within the char array to peek for a string
* @param peekAny The strings to peek for
* @param anyWhitespace A whitespace character in <code>peekAny</code>
* represents "any" whitespace character as defined in
* {@link #WHITESPACE}, or in Java Regex "\s".
*/
static final boolean peekAny(char[] sqlChars, int index, String[] peekAny, boolean anyWhitespace) {
for (String peek : peekAny)
if (peek(sqlChars, index, peek, anyWhitespace))
return true;
return false;
}
/**
* Create {@link QueryPart} objects from bind values or substitutes
*/
static final List<QueryPart> queryParts(Object... substitutes) {
// [#724] When bindings is null, this is probably due to API-misuse
// The user probably meant new Object[] { null }
if (substitutes == null) {
return queryParts(new Object[] { null });
}
else {
List<QueryPart> result = new ArrayList<QueryPart>();
for (Object substitute : substitutes) {
// [#1432] Distinguish between QueryParts and other objects
if (substitute instanceof QueryPart) {
result.add((QueryPart) substitute);
}
else {
@SuppressWarnings("unchecked")
Class<Object> type = (Class<Object>) (substitute != null ? substitute.getClass() : Object.class);
result.add(new Val<Object>(substitute, DSL.getDataType(type)));
}
}
return result;
}
}
/**
* Render a list of names of the <code>NamedQueryParts</code> contained in
* this list.
*/
static final void fieldNames(Context<?> context, Fields<?> fields) {
fieldNames(context, list(fields.fields));
}
/**
* Render a list of names of the <code>NamedQueryParts</code> contained in
* this list.
*/
static final void fieldNames(Context<?> context, Field<?>... fields) {
fieldNames(context, list(fields));
}
/**
* Render a list of names of the <code>NamedQueryParts</code> contained in
* this list.
*/
static final void fieldNames(Context<?> context, Collection<? extends Field<?>> list) {
String separator = "";
for (Field<?> field : list) {
context.sql(separator).visit(field.getUnqualifiedName());
separator = ", ";
}
}
/**
* Render a list of names of the <code>NamedQueryParts</code> contained in
* this list.
*/
static final void tableNames(Context<?> context, Table<?>... list) {
tableNames(context, list(list));
}
/**
* Render a list of names of the <code>NamedQueryParts</code> contained in
* this list.
*/
static final void tableNames(Context<?> context, Collection<? extends Table<?>> list) {
String separator = "";
for (Table<?> table : list) {
context.sql(separator).visit(table.getUnqualifiedName());
separator = ", ";
}
}
@SuppressWarnings("unchecked")
static final <T> T[] combine(T[] array, T value) {
T[] result = (T[]) java.lang.reflect.Array.newInstance(array.getClass().getComponentType(), array.length + 1);
System.arraycopy(array, 0, result, 0, array.length);
result[array.length] = value;
return result;
}
/**
* Combine a field with an array of fields
*/
static final Field<?>[] combine(Field<?> field, Field<?>... fields) {
if (fields == null) {
return new Field[] { field };
}
else {
Field<?>[] result = new Field<?>[fields.length + 1];
result[0] = field;
System.arraycopy(fields, 0, result, 1, fields.length);
return result;
}
}
/**
* Combine a field with an array of fields
*/
static final Field<?>[] combine(Field<?> field1, Field<?> field2, Field<?>... fields) {
if (fields == null) {
return new Field[] { field1, field2 };
}
else {
Field<?>[] result = new Field<?>[fields.length + 2];
result[0] = field1;
result[1] = field2;
System.arraycopy(fields, 0, result, 2, fields.length);
return result;
}
}
/**
* Combine a field with an array of fields
*/
static final Field<?>[] combine(Field<?> field1, Field<?> field2, Field<?> field3, Field<?>... fields) {
if (fields == null) {
return new Field[] { field1, field2, field3 };
}
else {
Field<?>[] result = new Field<?>[fields.length + 3];
result[0] = field1;
result[1] = field2;
result[2] = field3;
System.arraycopy(fields, 0, result, 3, fields.length);
return result;
}
}
/**
* Translate a {@link SQLException} to a {@link DataAccessException}
*/
static final DataAccessException translate(String sql, SQLException e) {
String message = "SQL [" + sql + "]; " + e.getMessage();
return new DataAccessException(message, e);
}
/**
* Safely close a statement
*/
static final void safeClose(ExecuteListener listener, ExecuteContext ctx) {
safeClose(listener, ctx, false);
}
/**
* Safely close a statement
*/
static final void safeClose(ExecuteListener listener, ExecuteContext ctx, boolean keepStatement) {
safeClose(listener, ctx, keepStatement, true);
}
/**
* Safely close a statement
*/
static final void safeClose(ExecuteListener listener, ExecuteContext ctx, boolean keepStatement, boolean keepResultSet) {
// [#2523] Set JDBC objects to null, to prevent repeated closing
JDBCUtils.safeClose(ctx.resultSet());
ctx.resultSet(null);
PreparedStatement statement = ctx.statement();
if (statement != null) {
consumeWarnings(ctx, listener);
}
// [#385] Close statements only if not requested to keep open
if (!keepStatement) {
if (statement != null) {
JDBCUtils.safeClose(statement);
ctx.statement(null);
}
// [#3234] We must ensure that any connection we may still have will be released,
// in the event of an exception
else {
Connection connection = localConnection();
if (connection != null) {
ctx.configuration().connectionProvider().release(connection);
}
}
}
// [#1868] [#2373] Terminate ExecuteListener lifecycle, if needed
if (keepResultSet)
listener.end(ctx);
// [#1326] Clean up any potentially remaining temporary lobs
DefaultExecuteContext.clean();
}
/**
* Type-safely copy a value from one record to another
*/
static final <T> void setValue(Record target, Field<T> targetField, Record source, Field<?> sourceField) {
setValue(target, targetField, source.get(sourceField));
}
/**
* Type-safely set a value to a record
*/
static final <T> void setValue(Record target, Field<T> targetField, Object value) {
target.set(targetField, targetField.getDataType().convert(value));
}
/**
* [#2591] Type-safely copy a value from one record to another, preserving flags.
*/
static final <T> void copyValue(AbstractRecord target, Field<T> targetField, Record source, Field<?> sourceField) {
DataType<T> targetType = targetField.getDataType();
int targetIndex = indexOrFail(target.fieldsRow(), targetField);
int sourceIndex = indexOrFail(source.fieldsRow(), sourceField);
target.values[targetIndex] = targetType.convert(source.get(sourceIndex));
target.originals[targetIndex] = targetType.convert(source.original(sourceIndex));
target.changed.set(targetIndex, source.changed(sourceIndex));
}
/**
* Map a {@link Catalog} according to the configured {@link org.jooq.SchemaMapping}
*/
@SuppressWarnings("deprecation")
static final Catalog getMappedCatalog(Configuration configuration, Catalog catalog) {
if (configuration != null) {
org.jooq.SchemaMapping mapping = configuration.schemaMapping();
if (mapping != null)
return mapping.map(catalog);
}
return catalog;
}
/**
* Map a {@link Schema} according to the configured {@link org.jooq.SchemaMapping}
*/
@SuppressWarnings("deprecation")
static final Schema getMappedSchema(Configuration configuration, Schema schema) {
if (configuration != null) {
org.jooq.SchemaMapping mapping = configuration.schemaMapping();
if (mapping != null)
return mapping.map(schema);
}
return schema;
}
/**
* Map a {@link Table} according to the configured {@link org.jooq.SchemaMapping}
*/
@SuppressWarnings("deprecation")
static final <R extends Record> Table<R> getMappedTable(Configuration configuration, Table<R> table) {
if (configuration != null) {
org.jooq.SchemaMapping mapping = configuration.schemaMapping();
if (mapping != null)
return mapping.map(table);
}
return table;
}
/**
* Map an {@link ArrayRecord} according to the configured {@link org.jooq.SchemaMapping}
*/
@SuppressWarnings("unchecked")
static final String getMappedUDTName(Configuration configuration, Class<? extends UDTRecord<?>> type) {
return getMappedUDTName(configuration, Tools.newRecord(false, (Class<UDTRecord<?>>) type).<RuntimeException>operate(null));
}
/**
* Map an {@link ArrayRecord} according to the configured {@link org.jooq.SchemaMapping}
*/
static final String getMappedUDTName(Configuration configuration, UDTRecord<?> record) {
UDT<?> udt = record.getUDT();
Schema mapped = getMappedSchema(configuration, udt.getSchema());
StringBuilder sb = new StringBuilder();
if (mapped != null)
sb.append(mapped.getName()).append('.');
sb.append(record.getUDT().getName());
return sb.toString();
}
private static final DSLContext CTX = DSL.using(new DefaultConfiguration());
/**
* Return a non-negative hash code for a {@link QueryPart}, taking into
* account FindBugs' <code>RV_ABSOLUTE_VALUE_OF_HASHCODE</code> pattern
*/
static final int hash(QueryPart part) {
// [#6025] Prevent unstable alias generation for derived tables due to
// inlined bind variables in hashCode() calculation
// [#6175] TODO: Speed this up with a faster way to calculate a hash code
return 0x7FFFFFF & CTX.render(part).hashCode();
}
/**
* Utility method to escape strings or "toString" other objects
*/
static final Field<String> escapeForLike(Object value) {
return escapeForLike(value, new DefaultConfiguration());
}
/**
* Utility method to escape strings or "toString" other objects
*/
static final Field<String> escapeForLike(Object value, Configuration configuration) {
if (value != null && value.getClass() == String.class) {
{
return val(escape("" + value, ESCAPE));
}
}
else {
return val("" + value);
}
}
/**
* Utility method to escape string fields, or cast other fields
*/
static final Field<String> escapeForLike(Field<?> field) {
return escapeForLike(field, new DefaultConfiguration());
}
/**
* Utility method to escape string fields, or cast other fields
*/
@SuppressWarnings("unchecked")
static final Field<String> escapeForLike(Field<?> field, Configuration configuration) {
if (nullSafe(field).getDataType().isString()) {
{
return escape((Field<String>) field, ESCAPE);
}
}
else {
return field.cast(String.class);
}
}
/**
* Utility method to check whether a field is a {@link Param}
*/
static final boolean isVal(Field<?> field) {
return field instanceof Param;
}
/**
* Utility method to extract a value from a field
*/
static final <T> T extractVal(Field<T> field) {
if (isVal(field)) {
return ((Param<T>) field).getValue();
}
else {
return null;
}
}
/**
* Add primary key conditions to a query
*/
@SuppressWarnings("deprecation")
static final void addConditions(org.jooq.ConditionProvider query, Record record, Field<?>... keys) {
for (Field<?> field : keys) {
addCondition(query, record, field);
}
}
/**
* Add a field condition to a query
*/
@SuppressWarnings("deprecation")
static final <T> void addCondition(org.jooq.ConditionProvider provider, Record record, Field<T> field) {
// [#2764] If primary keys are allowed to be changed, the
if (updatablePrimaryKeys(settings(record))) {
provider.addConditions(condition(field, record.original(field)));
}
else {
provider.addConditions(condition(field, record.get(field)));
}
}
/**
* Create a <code>null</code>-safe condition.
*/
static final <T> Condition condition(Field<T> field, T value) {
return (value == null) ? field.isNull() : field.eq(value);
}
// ------------------------------------------------------------------------
// XXX: [#2965] Reflection cache
// ------------------------------------------------------------------------
/**
* This API acts as a "guard" to prevent the same code from being executed
* recursively within the same thread.
*/
static class ThreadGuard {
/**
* The type of guard.
*/
static enum Guard {
RECORD_TOSTRING;
ThreadLocal<Object> tl = new ThreadLocal<Object>();
}
/**
* A guarded operation.
*/
static interface GuardedOperation<V> {
/**
* This callback is executed only once on the current stack.
*/
V unguarded();
/**
* This callback is executed if {@link #unguarded()} has already been executed on the current stack.
*/
V guarded();
}
/**
* A default implementation for {@link GuardedOperation#guarded()}.
*/
abstract static class AbstractGuardedOperation<V> implements GuardedOperation<V> {
@Override
public V guarded() {
return null;
}
}
/**
* Run an operation using a guard.
*/
static final <V> V run(Guard guard, GuardedOperation<V> operation) {
boolean unguarded = (guard.tl.get() == null);
if (unguarded)
guard.tl.set(Guard.class);
try {
if (unguarded)
return operation.unguarded();
else
return operation.guarded();
}
finally {
if (unguarded)
guard.tl.remove();
}
}
}
/**
* [#2965] This is a {@link Configuration}-based cache that can cache reflection information and other things
*/
static class Cache {
/**
* A callback wrapping expensive operations.
*/
static interface CachedOperation<V> {
/**
* An expensive operation.
*/
V call();
}
/**
* Run a {@link CachedOperation} in the context of a
* {@link Configuration}.
*
* @param configuration The configuration that may cache the outcome of
* the {@link CachedOperation}.
* @param operation The expensive operation.
* @param type The cache type to be used.
* @param keys The cache keys.
* @return The cached value or the outcome of the cached operation.
*/
@SuppressWarnings("unchecked")
static final <V> V run(Configuration configuration, CachedOperation<V> operation, String type, Serializable... keys) {
// If no configuration is provided take the default configuration that loads the default Settings
if (configuration == null)
configuration = new DefaultConfiguration();
// Shortcut caching when the relevant Settings flag isn't set.
if (!reflectionCaching(configuration.settings()))
return operation.call();
Map<Object, Object> cache = (Map<Object, Object>) configuration.data(type);
if (cache == null) {
// String synchronization is OK as all type literals were created using new String()
synchronized (type) {
cache = (Map<Object, Object>) configuration.data(type);
if (cache == null) {
cache = new ConcurrentHashMap<Object, Object>();
configuration.data(type, cache);
}
}
}
Object key = key(keys);
Object result = cache.get(key);
if (result == null) {
synchronized (cache) {
result = cache.get(key);
if (result == null) {
result = operation.call();
cache.put(key, result == null ? NULL : result);
}
}
}
return (V) (result == NULL ? null : result);
}
/**
* A <code>null</code> placeholder to be put in {@link ConcurrentHashMap}.
*/
private static final Object NULL = new Object();
/**
* Create a single-value or multi-value key for caching.
*/
private static final Object key(final Serializable... key) {
if (key == null || key.length == 0)
return key;
if (key.length == 1)
return key[0];
return new Key(key);
}
/**
* A multi-value key for caching.
*/
private static class Key implements Serializable {
/**
* Generated UID.
*/
private static final long serialVersionUID = 5822370287443922993L;
private final Serializable[] key;
Key(Serializable[] key) {
this.key = key;
}
@Override
public int hashCode() {
return Arrays.hashCode(key);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Key)
return Arrays.equals(key, ((Key) obj).key);
return false;
}
@Override
public String toString() {
return Arrays.asList(key).toString();
}
}
}
// ------------------------------------------------------------------------
// XXX: Reflection utilities used for POJO mapping
// ------------------------------------------------------------------------
/**
* Check if JPA classes can be loaded. This is only done once per JVM!
*/
private static final boolean isJPAAvailable() {
if (isJPAAvailable == null) {
try {
Class.forName(Column.class.getName());
isJPAAvailable = true;
}
catch (Throwable e) {
isJPAAvailable = false;
}
}
return isJPAAvailable;
}
/**
* Check whether <code>type</code> has any {@link Column} annotated members
* or methods
*/
static final boolean hasColumnAnnotations(final Configuration configuration, final Class<?> type) {
return Cache.run(configuration, new CachedOperation<Boolean>() {
@Override
public Boolean call() {
if (!isJPAAvailable())
return false;
// An @Entity or @Table usually has @Column annotations, too
if (type.getAnnotation(Entity.class) != null)
return true;
if (type.getAnnotation(javax.persistence.Table.class) != null)
return true;
for (java.lang.reflect.Field member : getInstanceMembers(type)) {
if (member.getAnnotation(Column.class) != null)
return true;
if (member.getAnnotation(Id.class) != null)
return true;
}
for (Method method : getInstanceMethods(type))
if (method.getAnnotation(Column.class) != null)
return true;
return false;
}
}, DATA_REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS, type);
}
/**
* Get all members annotated with a given column name
*/
static final List<java.lang.reflect.Field> getAnnotatedMembers(final Configuration configuration, final Class<?> type, final String name) {
return Cache.run(configuration, new CachedOperation<List<java.lang.reflect.Field>>() {
@Override
public List<java.lang.reflect.Field> call() {
List<java.lang.reflect.Field> result = new ArrayList<java.lang.reflect.Field>();
for (java.lang.reflect.Field member : getInstanceMembers(type)) {
Column column = member.getAnnotation(Column.class);
if (column != null) {
if (namesMatch(name, column.name())) {
result.add(accessible(member));
}
}
else {
Id id = member.getAnnotation(Id.class);
if (id != null) {
if (namesMatch(name, member.getName())) {
result.add(accessible(member));
}
}
}
}
return result;
}
}, DATA_REFLECTION_CACHE_GET_ANNOTATED_MEMBERS, type, name);
}
private static final boolean namesMatch(String name, String annotation) {
// [#4128] JPA @Column.name() properties are case-insensitive, unless
// the names are quoted using double quotes.
return annotation.startsWith("\"")
? ('"' + name + '"').equals(annotation)
: name.equalsIgnoreCase(annotation);
}
/**
* Get all members matching a given column name
*/
static final List<java.lang.reflect.Field> getMatchingMembers(final Configuration configuration, final Class<?> type, final String name) {
return Cache.run(configuration, new CachedOperation<List<java.lang.reflect.Field>>() {
@Override
public List<java.lang.reflect.Field> call() {
List<java.lang.reflect.Field> result = new ArrayList<java.lang.reflect.Field>();
// [#1942] Caching these values before the field-loop significantly
// accerates POJO mapping
String camelCaseLC = StringUtils.toCamelCaseLC(name);
for (java.lang.reflect.Field member : getInstanceMembers(type)) {
if (name.equals(member.getName())) {
result.add(accessible(member));
}
else if (camelCaseLC.equals(member.getName())) {
result.add(accessible(member));
}
}
return result;
}
}, DATA_REFLECTION_CACHE_GET_MATCHING_MEMBERS, type, name);
}
/**
* Get all setter methods annotated with a given column name
*/
static final List<Method> getAnnotatedSetters(final Configuration configuration, final Class<?> type, final String name) {
return Cache.run(configuration, new CachedOperation<List<Method>>() {
@Override
public List<Method> call() {
List<Method> result = new ArrayList<Method>();
for (Method method : getInstanceMethods(type)) {
Column column = method.getAnnotation(Column.class);
if (column != null && namesMatch(name, column.name())) {
// Annotated setter
if (method.getParameterTypes().length == 1) {
result.add(accessible(method));
}
// Annotated getter with matching setter
else if (method.getParameterTypes().length == 0) {
String m = method.getName();
String suffix = m.startsWith("get")
? m.substring(3)
: m.startsWith("is")
? m.substring(2)
: null;
if (suffix != null) {
try {
Method setter = type.getMethod("set" + suffix, method.getReturnType());
// Setter annotation is more relevant
if (setter.getAnnotation(Column.class) == null) {
result.add(accessible(setter));
}
}
catch (NoSuchMethodException ignore) {}
}
}
}
}
return result;
}
}, DATA_REFLECTION_CACHE_GET_ANNOTATED_SETTERS, type, name);
}
/**
* Get the first getter method annotated with a given column name
*/
static final Method getAnnotatedGetter(final Configuration configuration, final Class<?> type, final String name) {
return Cache.run(configuration, new CachedOperation<Method>() {
@Override
public Method call() {
for (Method method : getInstanceMethods(type)) {
Column column = method.getAnnotation(Column.class);
if (column != null && namesMatch(name, column.name())) {
// Annotated getter
if (method.getParameterTypes().length == 0) {
return accessible(method);
}
// Annotated setter with matching getter
else if (method.getParameterTypes().length == 1) {
String m = method.getName();
if (m.startsWith("set")) {
try {
Method getter = type.getMethod("get" + m.substring(3));
// Getter annotation is more relevant
if (getter.getAnnotation(Column.class) == null) {
return accessible(getter);
}
}
catch (NoSuchMethodException ignore) {}
try {
Method getter = type.getMethod("is" + m.substring(3));
// Getter annotation is more relevant
if (getter.getAnnotation(Column.class) == null) {
return accessible(getter);
}
}
catch (NoSuchMethodException ignore) {}
}
}
}
}
return null;
}
}, DATA_REFLECTION_CACHE_GET_ANNOTATED_GETTER, type, name);
}
/**
* Get all setter methods matching a given column name
*/
static final List<Method> getMatchingSetters(final Configuration configuration, final Class<?> type, final String name) {
return Cache.run(configuration, new CachedOperation<List<Method>>() {
@Override
public List<Method> call() {
List<Method> result = new ArrayList<Method>();
// [#1942] Caching these values before the method-loop significantly
// accerates POJO mapping
String camelCase = StringUtils.toCamelCase(name);
String camelCaseLC = StringUtils.toLC(camelCase);
for (Method method : getInstanceMethods(type)) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
if (name.equals(method.getName())) {
result.add(accessible(method));
}
else if (camelCaseLC.equals(method.getName())) {
result.add(accessible(method));
}
else if (("set" + name).equals(method.getName())) {
result.add(accessible(method));
}
else if (("set" + camelCase).equals(method.getName())) {
result.add(accessible(method));
}
}
}
return result;
}
}, DATA_REFLECTION_CACHE_GET_MATCHING_SETTERS, type, name);
}
/**
* Get the first getter method matching a given column name
*/
static final Method getMatchingGetter(final Configuration configuration, final Class<?> type, final String name) {
return Cache.run(configuration, new CachedOperation<Method>() {
@Override
public Method call() {
// [#1942] Caching these values before the method-loop significantly
// accerates POJO mapping
String camelCase = StringUtils.toCamelCase(name);
String camelCaseLC = StringUtils.toLC(camelCase);
for (Method method : getInstanceMethods(type)) {
if (method.getParameterTypes().length == 0) {
if (name.equals(method.getName())) {
return accessible(method);
}
else if (camelCaseLC.equals(method.getName())) {
return accessible(method);
}
else if (("get" + name).equals(method.getName())) {
return accessible(method);
}
else if (("get" + camelCase).equals(method.getName())) {
return accessible(method);
}
else if (("is" + name).equals(method.getName())) {
return accessible(method);
}
else if (("is" + camelCase).equals(method.getName())) {
return accessible(method);
}
}
}
return null;
}
}, DATA_REFLECTION_CACHE_GET_MATCHING_GETTER, type, name);
}
private static final List<Method> getInstanceMethods(Class<?> type) {
List<Method> result = new ArrayList<Method>();
for (Method method : type.getMethods()) {
if ((method.getModifiers() & Modifier.STATIC) == 0) {
result.add(method);
}
}
return result;
}
private static final List<java.lang.reflect.Field> getInstanceMembers(Class<?> type) {
List<java.lang.reflect.Field> result = new ArrayList<java.lang.reflect.Field>();
for (java.lang.reflect.Field field : type.getFields())
if ((field.getModifiers() & Modifier.STATIC) == 0)
result.add(field);
do {
for (java.lang.reflect.Field field : type.getDeclaredFields())
if ((field.getModifiers() & Modifier.STATIC) == 0)
result.add(field);
type = type.getSuperclass();
}
while (type != null);
return result;
}
/**
* Get a property name associated with a getter/setter method name.
*/
static final String getPropertyName(String methodName) {
String name = methodName;
if (name.startsWith("is") && name.length() > 2) {
name = name.substring(2, 3).toLowerCase() + name.substring(3);
}
else if (name.startsWith("get") && name.length() > 3) {
name = name.substring(3, 4).toLowerCase() + name.substring(4);
}
else if (name.startsWith("set") && name.length() > 3) {
name = name.substring(3, 4).toLowerCase() + name.substring(4);
}
return name;
}
// ------------------------------------------------------------------------
// XXX: JDBC helper methods
// ------------------------------------------------------------------------
/**
* [#3011] [#3054] Consume additional exceptions if there are any and append
* them to the <code>previous</code> exception's
* {@link SQLException#getNextException()} list.
*/
static final void consumeExceptions(Configuration configuration, PreparedStatement stmt, SQLException previous) {
}
/**
* [#3076] Consume warnings from a {@link Statement} and notify listeners.
*/
static final void consumeWarnings(ExecuteContext ctx, ExecuteListener listener) {
// [#3558] In some databases (e.g. MySQL), the call to PreparedStatement.getWarnings() issues
// a separate SHOW WARNINGS query. Users may want to avoid this query, explicitly
if (!Boolean.FALSE.equals(ctx.settings().isFetchWarnings())) {
try {
ctx.sqlWarning(ctx.statement().getWarnings());
}
// [#3741] In MySQL, calling SHOW WARNINGS on open streaming result sets can cause issues.
// while this has been resolved, we should expect the above call to cause other issues, too
catch (SQLException e) {
ctx.sqlWarning(new SQLWarning("Could not fetch SQLWarning", e));
}
}
if (ctx.sqlWarning() != null)
listener.warning(ctx);
}
/**
* [#5666] Handle the complexity of each dialect's understanding of
* correctly calling {@link Statement#execute()}.
*/
static final void executeStatementAndGetFirstResultSet(ExecuteContext ctx) throws SQLException {
PreparedStatement stmt = ctx.statement();
if (stmt.execute()) {
ctx.resultSet(stmt.getResultSet());
}
else {
ctx.resultSet(null);
ctx.rows(stmt.getUpdateCount());
}
}
/**
* [#3681] Consume all {@link ResultSet}s from a JDBC {@link Statement}.
*/
static final void consumeResultSets(ExecuteContext ctx, ExecuteListener listener, Results results, Intern intern) throws SQLException {
boolean anyResults = false;
int i = 0;
int rows = (ctx.resultSet() == null) ? ctx.rows() : 0;
for (i = 0; i < maxConsumedResults; i++) {
if (ctx.resultSet() != null) {
anyResults = true;
Field<?>[] fields = new MetaDataFieldProvider(ctx.configuration(), ctx.resultSet().getMetaData()).getFields();
Cursor<Record> c = new CursorImpl<Record>(ctx, listener, fields, intern != null ? intern.internIndexes(fields) : null, true, false);
results.resultsOrRows().add(new ResultsImpl.ResultOrRowsImpl(c.fetch()));
}
else {
if (rows != -1)
results.resultsOrRows().add(new ResultsImpl.ResultOrRowsImpl(rows));
else
break;
}
if (ctx.statement().getMoreResults()) {
ctx.resultSet(ctx.statement().getResultSet());
}
else {
rows = ctx.statement().getUpdateCount();
ctx.rows(rows);
if (rows != -1)
ctx.resultSet(null);
else
break;
}
}
if (i == maxConsumedResults)
log.warn("Maximum consumed results reached: " + maxConsumedResults + ". This is probably a bug. Please report to https://github.com/jOOQ/jOOQ/issues/new");
// Call this only when there was at least one ResultSet.
// Otherwise, this call is not supported by ojdbc or CUBRID [#4440]
if (anyResults && ctx.family() != CUBRID)
ctx.statement().getMoreResults(Statement.CLOSE_ALL_RESULTS);
}
private static final Pattern NEW_LINES = Pattern.compile("[\\r\\n]+");
static final List<String[]> parseTXT(String string, String nullLiteral) {
String[] strings = NEW_LINES.split(string);
if (strings.length < 2) {
throw new DataAccessException("String must contain at least two lines");
}
// [#2235] Distinguish between jOOQ's Result.format() and H2's format
boolean formatted = (string.charAt(0) == '+');
// In jOOQ's Result.format(), that's line number one:
// 1: +----+------+----+
// 2: |ABC |XYZ |HEHE|
// 3: +----+------+----+
// 4: |Data|{null}|1234|
// 5: +----+------+----+
// 012345678901234567
// resulting in
// [{1,5} {6,12} {13,17}]
if (formatted) {
return parseTXTLines(nullLiteral, strings, PLUS_PATTERN, 0, 1, 3, strings.length - 1);
}
// In H2 format, that's line number two:
// 1: ABC XYZ HEHE
// 2: ----- ------- ----
// 3: Data {null} 1234
// 0123456789012345678
// resulting in
// [{0,5} {7,14} {15,19}]
else {
return parseTXTLines(nullLiteral, strings, DASH_PATTERN, 1, 0, 2, strings.length);
}
}
private static final List<String[]> parseTXTLines(
String nullLiteral,
String[] strings,
Pattern pattern,
int matchLine,
int headerLine,
int dataLineStart,
int dataLineEnd) {
List<int[]> positions = new ArrayList<int[]>();
Matcher m = pattern.matcher(strings[matchLine]);
while (m.find()) {
positions.add(new int[] { m.start(1), m.end(1) });
}
// Parse header line and data lines into string arrays
List<String[]> result = new ArrayList<String[]>();
parseTXTLine(positions, result, strings[headerLine], nullLiteral);
for (int j = dataLineStart; j < dataLineEnd; j++) {
parseTXTLine(positions, result, strings[j], nullLiteral);
}
return result;
}
private static final void parseTXTLine(List<int[]> positions, List<String[]> result, String string, String nullLiteral) {
String[] fields = new String[positions.size()];
result.add(fields);
int length = string.length();
for (int i = 0; i < fields.length; i++) {
int[] position = positions.get(i);
if (position[0] < length) {
fields[i] = string.substring(position[0], Math.min(position[1], length)).trim();
}
else {
fields[i] = null;
}
if (StringUtils.equals(fields[i], nullLiteral)) {
fields[i] = null;
}
}
}
private static final Pattern P_PARSE_HTML_ROW = Pattern.compile("<tr>(.*?)</tr>");
private static final Pattern P_PARSE_HTML_COL_HEAD = Pattern.compile("<th>(.*?)</th>");
private static final Pattern P_PARSE_HTML_COL_BODY = Pattern.compile("<td>(.*?)</td>");
static final List<String[]> parseHTML(String string) {
List<String[]> result = new ArrayList<String[]>();
Matcher mRow = P_PARSE_HTML_ROW.matcher(string);
while (mRow.find()) {
String row = mRow.group(1);
List<String> col = new ArrayList<String>();
// Header was not yet emitted
if (result.isEmpty()) {
Matcher mColHead = P_PARSE_HTML_COL_HEAD.matcher(row);
while (mColHead.find())
col.add(mColHead.group(1));
}
if (col.isEmpty()) {
Matcher mColBody = P_PARSE_HTML_COL_BODY.matcher(row);
while (mColBody.find())
col.add(mColBody.group(1));
if (result.isEmpty())
result.add(fieldNameStrings(col.size()));
}
result.add(col.toArray(EMPTY_STRING));
}
return result;
}
/**
* Wrap a <code>DROP .. IF EXISTS</code> statement with
* <code>BEGIN EXECUTE IMMEDIATE '...' EXCEPTION WHEN ... END;</code>, if
* <code>IF EXISTS</code> is not supported.
*/
static final void executeImmediateBegin(Context<?> ctx, DDLStatementType type) {
switch (ctx.family()) {
case FIREBIRD: {
ctx.visit(K_EXECUTE_BLOCK).formatSeparator()
.visit(K_AS).formatSeparator()
.visit(K_BEGIN).formatIndentStart().formatSeparator()
.visit(K_EXECUTE_STATEMENT).sql(" '").stringLiteral(true).formatIndentStart().formatSeparator();
break;
}
default:
break;
}
}
/**
* Wrap a <code>DROP .. IF EXISTS</code> statement with
* <code>BEGIN EXECUTE IMMEDIATE '...' EXCEPTION WHEN ... END;</code>, if
* <code>IF EXISTS</code> is not supported.
*/
static final void executeImmediateEnd(Context<?> ctx, DDLStatementType type) {
boolean drop = asList(DROP_INDEX, DROP_SEQUENCE, DROP_TABLE, DROP_VIEW).contains(type);
switch (ctx.family()) {
case FIREBIRD: {
ctx.formatIndentEnd().formatSeparator().stringLiteral(false).sql("';").formatSeparator()
.visit(K_WHEN).sql(" sqlcode -607 ").visit(K_DO).formatIndentStart().formatSeparator()
.visit(K_BEGIN).sql(' ').visit(K_END).formatIndentEnd().formatIndentEnd().formatSeparator()
.visit(K_END);
break;
}
default:
break;
}
}
static final void executeImmediateIfExistsBegin(Context<?> ctx, DDLStatementType type, QueryPart object) {
switch (ctx.family()) {
default:
executeImmediateBegin(ctx, type);
break;
}
}
static final void executeImmediateIfExistsEnd(Context<?> ctx, DDLStatementType type, QueryPart object) {
switch (ctx.family()) {
default:
executeImmediateEnd(ctx, type);
break;
}
}
static final void toSQLDDLTypeDeclaration(Context<?> ctx, DataType<?> type) {
String typeName = type.getTypeName(ctx.configuration());
// In some databases, identity is a type, not a flag.
if (type.identity()) {
switch (ctx.family()) {
case POSTGRES: ctx.visit(type.getType() == Long.class ? K_SERIAL8 : K_SERIAL); return;
}
}
if (type.hasLength()) {
if (type.length() > 0) {
ctx.sql(typeName).sql('(').sql(type.length()).sql(')');
}
// Some databases don't allow for length-less VARCHAR, VARBINARY types
else {
String castTypeName = type.getCastTypeName(ctx.configuration());
if (!typeName.equals(castTypeName))
ctx.sql(castTypeName);
else
ctx.sql(typeName);
}
}
else if (type.hasPrecision() && type.precision() > 0) {
if (type.hasScale())
ctx.sql(typeName).sql('(').sql(type.precision()).sql(", ").sql(type.scale()).sql(')');
else
ctx.sql(typeName).sql('(').sql(type.precision()).sql(')');
}
else {
ctx.sql(typeName);
}
if (type.identity()) {
switch (ctx.family()) {
case CUBRID: ctx.sql(' ').visit(K_AUTO_INCREMENT); break;
case DERBY: ctx.sql(' ').visit(K_GENERATED_BY_DEFAULT_AS_IDENTITY); break;
case HSQLDB: ctx.sql(' ').visit(K_GENERATED_BY_DEFAULT_AS_IDENTITY).sql('(').visit(K_START_WITH).sql(" 1)"); break;
}
}
}
// -------------------------------------------------------------------------
// XXX: ForkJoinPool ManagedBlock implementation
// -------------------------------------------------------------------------
static <T> Supplier<T> blocking(Supplier<T> supplier) {
return blocking(supplier, false);
}
static <T> Supplier<T> blocking(Supplier<T> supplier, boolean threadLocal) {
// [#5377] In ThreadLocal contexts (e.g. when using ThreadLocalTransactionprovider),
// no ManagedBlocker is needed as we're guaranteed by API contract to always
// remain on the same thread.
return threadLocal ? supplier : new Supplier<T>() {
volatile T asyncResult;
@Override
public T get() {
try {
ForkJoinPool.managedBlock(new ManagedBlocker() {
@Override
public boolean block() {
asyncResult = supplier.get();
return true;
}
@Override
public boolean isReleasable() {
return asyncResult != null;
}
});
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
return asyncResult;
}
};
}
static <E extends EnumType> EnumType[] enums(Class<? extends E> type) {
// Java implementation
if (Enum.class.isAssignableFrom(type)) {
return type.getEnumConstants();
}
// [#4427] Scala implementation
else {
try {
// There's probably a better way to do this:
// http://stackoverflow.com/q/36068089/521799
Class<?> companionClass = Thread.currentThread().getContextClassLoader().loadClass(type.getName() + "$");
java.lang.reflect.Field module = companionClass.getField("MODULE$");
Object companion = module.get(companionClass);
return (EnumType[]) companionClass.getMethod("values").invoke(companion);
}
catch (Exception e) {
throw new MappingException("Error while looking up Scala enum", e);
}
}
}
/**
* Whether a Java type is suitable for {@link Types#TIME}.
*/
static final boolean isTime(Class<?> t) {
return t == Time.class || t == LocalTime.class ;
}
/**
* Whether a Java type is suitable for {@link Types#TIMESTAMP}.
*/
static final boolean isTimestamp(Class<?> t) {
return t == Timestamp.class || t == LocalDateTime.class ;
}
/**
* Whether a Java type is suitable for {@link Types#DATE}.
*/
static final boolean isDate(Class<?> t) {
return t == Date.class || t == LocalDate.class ;
}
static final boolean hasAmbiguousNames(Collection<? extends Field<?>> fields) {
if (fields == null)
return false;
Set<String> names = new HashSet<String>();
for (Field<?> field : fields)
if (!names.add(field.getName()))
return true;
return false;
}
static final <T> Field<T> qualify(Field<T> field, Table<?> table) {
Field<T> result = table.field(field);
if (result != null)
return result;
Name[] part = table.getQualifiedName().parts();
Name[] name = new Name[part.length + 1];
System.arraycopy(part, 0, name, 0, part.length);
name[part.length] = field.getUnqualifiedName();
return DSL.field(DSL.name(name), field.getDataType());
}
}