diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java new file mode 100644 index 0000000000..a24691981b --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -0,0 +1,3141 @@ +/** + * Copyright (c) 2009-2016, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import static java.lang.Boolean.FALSE; +import static java.util.Arrays.asList; +// ... +import static org.jooq.SQLDialect.CUBRID; +import static org.jooq.SQLDialect.MARIADB; +import static org.jooq.SQLDialect.MYSQL; +import static org.jooq.conf.BackslashEscaping.DEFAULT; +import static org.jooq.conf.BackslashEscaping.ON; +import static org.jooq.conf.ParamType.INLINED; +import static org.jooq.conf.ParamType.NAMED; +import static org.jooq.conf.ParamType.NAMED_OR_INLINED; +import static org.jooq.conf.SettingsTools.getBackslashEscaping; +import static org.jooq.conf.SettingsTools.reflectionCaching; +import static org.jooq.conf.SettingsTools.updatablePrimaryKeys; +import static org.jooq.impl.DDLStatementType.CREATE_INDEX; +import static org.jooq.impl.DDLStatementType.CREATE_SEQUENCE; +import static org.jooq.impl.DDLStatementType.CREATE_TABLE; +import static org.jooq.impl.DDLStatementType.CREATE_VIEW; +import static org.jooq.impl.DDLStatementType.DROP_INDEX; +import static org.jooq.impl.DDLStatementType.DROP_SEQUENCE; +import static org.jooq.impl.DDLStatementType.DROP_TABLE; +import static org.jooq.impl.DDLStatementType.DROP_VIEW; +import static org.jooq.impl.DSL.concat; +import static org.jooq.impl.DSL.escape; +import static org.jooq.impl.DSL.getDataType; +import static org.jooq.impl.DSL.name; +import static org.jooq.impl.DSL.nullSafe; +import static org.jooq.impl.DSL.val; +import static org.jooq.impl.DefaultExecuteContext.localConnection; +import static org.jooq.impl.Identifiers.QUOTES; +import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER; +import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER_ESCAPED; +import static org.jooq.impl.Identifiers.QUOTE_START_DELIMITER; +import static org.jooq.tools.reflect.Reflect.accessible; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinPool.ManagedBlocker; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +// ... +import org.jooq.Attachable; +import org.jooq.AttachableInternal; +import org.jooq.BindContext; +import org.jooq.Catalog; +import org.jooq.Clause; +import org.jooq.Condition; +import org.jooq.Configuration; +import org.jooq.Context; +import org.jooq.Cursor; +import org.jooq.DSLContext; +import org.jooq.DataType; +import org.jooq.ExecuteContext; +import org.jooq.ExecuteListener; +import org.jooq.Field; +import org.jooq.Name; +import org.jooq.Param; +import org.jooq.QueryPart; +import org.jooq.Record; +import org.jooq.RecordType; +import org.jooq.RenderContext; +import org.jooq.RenderContext.CastMode; +import org.jooq.Result; +import org.jooq.Results; +import org.jooq.Row; +import org.jooq.SQLDialect; +import org.jooq.Schema; +import org.jooq.SelectField; +import org.jooq.Table; +import org.jooq.UDT; +import org.jooq.UDTRecord; +import org.jooq.UpdatableRecord; +import org.jooq.conf.BackslashEscaping; +import org.jooq.conf.Settings; +import org.jooq.exception.DataAccessException; +import org.jooq.exception.TooManyRowsException; +import org.jooq.impl.Tools.Cache.CachedOperation; +import org.jooq.tools.JooqLogger; +import org.jooq.tools.StringUtils; +import org.jooq.tools.jdbc.JDBCUtils; +import org.jooq.tools.reflect.Reflect; +import org.jooq.types.UByte; +import org.jooq.types.UInteger; +import org.jooq.types.ULong; +import org.jooq.types.UShort; + +/** + * General internal jOOQ utilities + * + * @author Lukas Eder + */ +final class Tools { + + static final JooqLogger log = JooqLogger.getLogger(Tools.class); + + // ------------------------------------------------------------------------ + // Some constants for use with Context.data() + // ------------------------------------------------------------------------ + + enum DataKey { + + /** + * [#1537] This constant is used internally by jOOQ to omit the RETURNING + * clause in {@link DSLContext#batchStore(UpdatableRecord...)} calls for + * {@link SQLDialect#POSTGRES}. + */ + DATA_OMIT_RETURNING_CLAUSE, + + /** + * [#1905] This constant is used internally by jOOQ to indicate to + * subqueries that they're being rendered in the context of a row value + * expression predicate. + *

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

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

+ */ + DATA_FORCE_STATIC_STATEMENT, + + /** + * [#2665] Omit the emission of clause events by {@link QueryPart}s. + *

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

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

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

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

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

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