org.jooq.impl.Utils Maven / Gradle / Ivy
/**
* Copyright (c) 2009-2014, Data Geekery GmbH (http://www.datageekery.com)
* All rights reserved.
*
* This work is dual-licensed
* - under the Apache Software License 2.0 (the "ASL")
* - under the jOOQ License and Maintenance Agreement (the "jOOQ License")
* =============================================================================
* You may choose which license applies to you:
*
* - If you're using this work with Open Source databases, you may choose
* either ASL or jOOQ License.
* - If you're using this work with at least one commercial database, you must
* choose jOOQ License
*
* For more information, please visit http://www.jooq.org/licenses
*
* Apache Software License 2.0:
* -----------------------------------------------------------------------------
* 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.
*
* jOOQ License and Maintenance Agreement:
* -----------------------------------------------------------------------------
* Data Geekery grants the Customer the non-exclusive, timely limited and
* non-transferable license to install and use the Software under the terms of
* the jOOQ License and Maintenance Agreement.
*
* This library is distributed with a LIMITED WARRANTY. See the jOOQ License
* and Maintenance Agreement for more details: http://www.jooq.org/licensing
*/
package org.jooq.impl;
import static java.lang.Boolean.FALSE;
// ...
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.DSL.concat;
import static org.jooq.impl.DSL.escape;
import static org.jooq.impl.DSL.fieldByName;
import static org.jooq.impl.DSL.getDataType;
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.DropStatementType.INDEX;
import static org.jooq.impl.DropStatementType.SEQUENCE;
import static org.jooq.impl.DropStatementType.TABLE;
import static org.jooq.impl.DropStatementType.VIEW;
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.Method;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
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.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.Column;
import javax.persistence.Entity;
// ...
import org.jooq.Attachable;
import org.jooq.AttachableInternal;
import org.jooq.BindContext;
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.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.Result;
import org.jooq.Row;
import org.jooq.SQLDialect;
import org.jooq.Schema;
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.InvalidResultException;
import org.jooq.impl.Utils.Cache.CachedOperation;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
import org.jooq.tools.jdbc.JDBCUtils;
import org.jooq.tools.reflect.Reflect;
/**
* General internal jOOQ utilities
*
* @author Lukas Eder
*/
final class Utils {
static final JooqLogger log = JooqLogger.getLogger(Utils.class);
// ------------------------------------------------------------------------
// Some constants for use with Context.data()
// ------------------------------------------------------------------------
/**
* [#1537] This constant is used internally by jOOQ to omit the RETURNING
* clause in {@link DSLContext#batchStore(UpdatableRecord...)} calls for
* {@link SQLDialect#POSTGRES}.
*/
static final String DATA_OMIT_RETURNING_CLAUSE = "org.jooq.configuration.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
*/
static final String DATA_ROW_VALUE_EXPRESSION_PREDICATE_SUBQUERY = "org.jooq.configuration.row-value-expression-subquery";
/**
* [#1296] This constant is used internally by jOOQ to indicate that
* {@link ResultSet} rows must be locked to simulate a
* FOR UPDATE
clause.
*/
static final String DATA_LOCK_ROWS_FOR_UPDATE = "org.jooq.configuration.lock-rows-for-update";
/**
* [#1520] Count the number of bind values, and potentially enforce a static
* statement.
*/
static final String DATA_COUNT_BIND_VALUES = "org.jooq.configuration.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
*
*/
static final String DATA_FORCE_STATIC_STATEMENT = "org.jooq.configuration.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}.
*/
static final String DATA_OMIT_CLAUSE_EVENT_EMISSION = "org.jooq.configuration.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.
*/
static final String DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES = "org.jooq.configuration.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.
*/
static final String DATA_LOCALLY_SCOPED_DATA_MAP = "org.jooq.configuration.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.
*/
static final String DATA_WINDOW_DEFINITIONS = "org.jooq.configuration.local-window-definitions";
/* [pro] xx
xxx
x xxxxxxx xxxxxxxxx xxxxxxxxx xxx xxx xxxxx xxxxx xxxxxxx
x xxx
x xx xxxx x xxxxxxxxxxx xxxxx xxxxxxx xxxxxxxxxxx xxxxxx xxxxxxx xxxxx
x xxxxxxxxxxx xx xxx xxxxxxxxxx xxxxxxxxxxxx xx xxxxxxxxxxxxxxxx xxxxxxx
xx
xxxxxx xxxxx xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xx [/pro] */
/**
* [#1629] The {@link Connection#getAutoCommit()} flag value before starting
* a new transaction.
*/
static final String DATA_DEFAULT_TRANSACTION_PROVIDER_AUTOCOMMIT = "org.jooq.configuration.default-transaction-provider-autocommit";
/**
* [#1629] The {@link Connection#getAutoCommit()} flag value before starting
* a new transaction.
*/
static final String DATA_DEFAULT_TRANSACTION_PROVIDER_SAVEPOINTS = "org.jooq.configuration.default-transaction-provider-savepoints";
/**
* [#1629] The {@link DefaultConnectionProvider} instance to be used during
* the transaction.
*/
static final String DATA_DEFAULT_TRANSACTION_PROVIDER_CONNECTION = "org.jooq.configuration.default-transaction-provider-connection-provider";
/**
* [#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.
*/
static final String DATA_OVERRIDE_ALIASES_IN_ORDER_BY = "org.jooq.configuration.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.
*/
static final String DATA_UNALIAS_ALIASES_IN_ORDER_BY = "org.jooq.configuration.unalias-aliases-in-order-by";
/**
* [#3381] The table to be used for the {@link Clause#SELECT_INTO} clause.
*/
static final String DATA_SELECT_INTO_TABLE = "org.jooq.configuration.select-into-table";
/**
* [#3381] Omit the {@link Clause#SELECT_INTO}, as it is being emulated.
*/
static final String DATA_OMIT_INTO_CLAUSE = "org.jooq.configuration.omit-into-clause";
/**
* [#1658] Specify whether the trailing LIMIT clause needs to be rendered.
*/
static final String DATA_RENDER_TRAILING_LIMIT_IF_APPLICABLE = "org.jooq.configuration.render-trailing-limit-if-applicable";
/**
* [#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;
/**
* 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;
}
/* [pro] xx
xxx
x xxxxxx x xxx xxxxxxxxxxxx xxxxxx xxxxxx xxxxxxxxxxxx
xx
xxxxxx xxxxx xx xxxxxxx xxxxxxxxxxxxxxx x xxxxxxxxxxxxxxxxxxxxxxx xxxxx x
xxx x
xxxxxx xxxxxxxxxxxxxxxxxxx
x
xxxxx xxxxxxxxxx xx x
xxxxx xxx xxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxx xxxx xxxx xxx xxxxxxx x xxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxx x x x xxxx
x xx xxxxxxxxx x x x xxxxxxxxxxxxxxxx
x
x
xx [/pro] */
/**
* 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
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
static final RecordDelegate newRecord(boolean fetched, Class type, Field>[] fields, Configuration configuration) {
try {
R record;
// An ad-hoc type resulting from a JOIN or arbitrary SELECT
if (type == RecordImpl.class || type == Record.class) {
record = (R) new RecordImpl(fields);
}
// Any generated record
else {
// [#919] Allow for accessing non-public constructors
record = Reflect.accessible(type.getDeclaredConstructor()).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);
}
}
/**
* [#2700] [#3582] If a POJO attribute is NULL, but the column is NOT NULL
* then we should let the database apply DEFAULT values
*/
static void resetChangedOnNotNull(Record record) {
int size = record.size();
for (int i = 0; i < size; i++)
if (record.getValue(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 Configuration configuration(Attachable attachable) {
return configuration(attachable instanceof AttachableInternal
? ((AttachableInternal) attachable).configuration()
: null);
}
/**
* Get a configuration or a new {@link DefaultConfiguration} if
* null
.
*/
static Configuration configuration(Configuration configuration) {
return configuration != null ? configuration : new DefaultConfiguration();
}
/**
* Get a configuration's settings or default settings if the configuration
* is null
.
*/
static Settings settings(Attachable attachable) {
return configuration(attachable).settings();
}
/**
* Get a configuration's settings or default settings if the configuration
* is null
.
*/
static 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(new Field[fields.size()]);
}
// ------------------------------------------------------------------------
// 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) {
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] = fieldByName(names[i]);
return result;
}
static final Field>[] aliasedFields(Field>[] fields, String[] aliases) {
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) {
Field>[] result = new Field[fieldNames.length];
for (int i = 0; i < fieldNames.length; i++)
if (tableName == null)
result[i] = fieldByName(fieldNames[i]);
else
result[i] = fieldByName(tableName, fieldNames[i]);
return result;
}
static final Name[] names(String[] names) {
Name[] result = new Name[names.length];
for (int i = 0; i < names.length; i++)
result[i] = DSL.name(names[i]);
return result;
}
/**
* 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;
}
else {
return val(value);
}
}
/**
* 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 Field field(Object value, Field field) {
// Fields can be mixed with constant values
if (value instanceof Field>) {
return (Field) 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 Field field(Object value, Class type) {
// Fields can be mixed with constant values
if (value instanceof Field>) {
return (Field) 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 Field field(Object value, DataType type) {
// Fields can be mixed with constant values
if (value instanceof Field>) {
return (Field) 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 List> fields(Object[] values) {
List> result = new ArrayList>();
if (values != null) {
for (Object 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> fields(Object[] values, Field> field) {
List> result = new ArrayList>();
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> fields(Object[] values, Field>[] fields) {
List> result = new ArrayList>();
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> fields(Object[] values, Class> type) {
List> result = new ArrayList>();
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> fields(Object[] values, Class>[] types) {
List> result = new ArrayList>();
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> fields(Object[] values, DataType> type) {
List> result = new ArrayList>();
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> fields(Object[] values, DataType>[] types) {
List> result = new ArrayList>();
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> inline(T[] values) {
List> result = new ArrayList>();
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> unqualify(List extends Field>> fields) {
QueryPartList> result = new QueryPartList>();
for (Field> field : fields)
result.add(fieldByName(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 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;
}
/**
* Create a new array
*/
static final T[] array(T... array) {
return array;
}
/**
* Use this rather than {@link Arrays#asList(Object...)} for
* null
-safety
*/
static final List list(T... array) {
return array == null ? Collections.emptyList() : Arrays.asList(array);
}
/**
* Turn a {@link Record} into a {@link Map}
*/
static final Map, Object> map(Record record) {
Map, Object> result = new LinkedHashMap, Object>();
int size = record.size();
for (int i = 0; i < size; i++) {
result.put(record.field(i), record.getValue(i));
}
return result;
}
/**
* Extract the first item from an iterable or null
, if there is
* no such item, or if iterable itself is null
*/
static final 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 null
, or throw an
* exception
*
* @param list The list
* @return The only element from the list or null
* @throws InvalidResultException Thrown if the list contains more than one
* element
*/
static final R filterOne(List list) throws InvalidResultException {
int size = list.size();
if (size == 1) {
return list.get(0);
}
else if (size > 1) {
throw new InvalidResultException("Too many rows selected : " + size);
}
return null;
}
/**
* Get the only element from a cursor or null
, or throw an
* exception.
*
* [#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 null
* @throws InvalidResultException Thrown if the cursor returns more than one
* element
*/
static final R fetchOne(Cursor cursor) throws InvalidResultException {
try {
R record = cursor.fetchOne();
if (cursor.hasNext()) {
throw new InvalidResultException("Cursor returned more than one result");
}
return record;
}
finally {
cursor.close();
}
}
/**
* Visit each query part from a collection, given a context.
*/
static final > 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 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
*
* This will perform two actions:
*
* - 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
* - 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
*
*/
@SuppressWarnings("null")
static final void renderAndBind(Context> ctx, String sql, List 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();
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());
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, "--")) {
// 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 (sqlChars[i] == '?' && substituteIndex < substitutes.size()) {
QueryPart substitute = substitutes.get(substituteIndex++);
if (render.paramType() == INLINED || render.paramType() == NAMED || render.paramType() == NAMED_OR_INLINED) {
render.visit(substitute);
}
else {
render.sql(sqlChars[i]);
}
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.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 index
of a char[]
*
* @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 index
of a char[]
*
* @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 peekAny
* 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 index
of a char[]
*
* @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 index
of a
* char[]
*
* @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 peekAny
* 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 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 result = new ArrayList();
for (Object substitute : substitutes) {
// [#1432] Distinguish between QueryParts and other objects
if (substitute instanceof QueryPart) {
result.add((QueryPart) substitute);
}
else {
@SuppressWarnings("unchecked")
Class