2339 lines
78 KiB
Java
2339 lines
78 KiB
Java
/**
|
|
* Copyright (c) 2009-2013, Lukas Eder, lukas.eder@gmail.com
|
|
* All rights reserved.
|
|
*
|
|
* This software is licensed to you under the Apache License, Version 2.0
|
|
* (the "License"); You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* . Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
*
|
|
* . Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* . Neither the name "jOOQ" nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
package org.jooq.impl;
|
|
|
|
import static java.lang.Boolean.FALSE;
|
|
import static org.jooq.SQLDialect.CUBRID;
|
|
import static org.jooq.SQLDialect.POSTGRES;
|
|
import static org.jooq.impl.Factory.escape;
|
|
import static org.jooq.impl.Factory.getDataType;
|
|
import static org.jooq.impl.Factory.nullSafe;
|
|
import static org.jooq.impl.Factory.val;
|
|
import static org.jooq.tools.jdbc.JDBCUtils.safeFree;
|
|
import static org.jooq.tools.jdbc.JDBCUtils.wasNull;
|
|
import static org.jooq.tools.reflect.Reflect.accessible;
|
|
import static org.jooq.tools.reflect.Reflect.on;
|
|
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Modifier;
|
|
import java.math.BigDecimal;
|
|
import java.math.BigInteger;
|
|
import java.sql.Array;
|
|
import java.sql.Blob;
|
|
import java.sql.CallableStatement;
|
|
import java.sql.Clob;
|
|
import java.sql.Connection;
|
|
import java.sql.Date;
|
|
import java.sql.ResultSet;
|
|
import java.sql.SQLException;
|
|
import java.sql.SQLInput;
|
|
import java.sql.SQLOutput;
|
|
import java.sql.Time;
|
|
import java.sql.Timestamp;
|
|
import java.text.ParseException;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Calendar;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.UUID;
|
|
import java.util.regex.Pattern;
|
|
|
|
import javax.persistence.Column;
|
|
import javax.persistence.Entity;
|
|
|
|
import org.jooq.ArrayRecord;
|
|
import org.jooq.Attachable;
|
|
import org.jooq.AttachableInternal;
|
|
import org.jooq.BindContext;
|
|
import org.jooq.Configuration;
|
|
import org.jooq.Converter;
|
|
import org.jooq.Cursor;
|
|
import org.jooq.DataType;
|
|
import org.jooq.EnumType;
|
|
import org.jooq.ExecuteContext;
|
|
import org.jooq.ExecuteListener;
|
|
import org.jooq.Field;
|
|
import org.jooq.Param;
|
|
import org.jooq.QueryPart;
|
|
import org.jooq.Record;
|
|
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.Settings;
|
|
import org.jooq.exception.DataAccessException;
|
|
import org.jooq.exception.InvalidResultException;
|
|
import org.jooq.tools.Convert;
|
|
import org.jooq.tools.JooqLogger;
|
|
import org.jooq.tools.LoggerListener;
|
|
import org.jooq.tools.StopWatchListener;
|
|
import org.jooq.tools.StringUtils;
|
|
import org.jooq.tools.jdbc.JDBCUtils;
|
|
import org.jooq.tools.reflect.Reflect;
|
|
import org.jooq.types.DayToSecond;
|
|
import org.jooq.types.UByte;
|
|
import org.jooq.types.UInteger;
|
|
import org.jooq.types.ULong;
|
|
import org.jooq.types.UNumber;
|
|
import org.jooq.types.UShort;
|
|
import org.jooq.types.YearToMonth;
|
|
import org.jooq.util.postgres.PostgresUtils;
|
|
|
|
/**
|
|
* General internal jOOQ utilities
|
|
*
|
|
* @author Lukas Eder
|
|
*/
|
|
final class Utils {
|
|
|
|
static final JooqLogger log = JooqLogger.getLogger(Utils.class);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Some constants for use with Configuration.setData()
|
|
// ------------------------------------------------------------------------
|
|
|
|
/**
|
|
* [#1537] This constant is used internally by jOOQ to omit the RETURNING
|
|
* clause in {@link Executor#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.
|
|
* <p>
|
|
* 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";
|
|
|
|
// ------------------------------------------------------------------------
|
|
// 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;
|
|
|
|
/**
|
|
* A pattern for the JDBC escape syntax
|
|
*/
|
|
private static final Pattern JDBC_ESCAPE_PATTERN = Pattern.compile("\\{(fn|d|t|ts)\\b.*");
|
|
|
|
// ------------------------------------------------------------------------
|
|
// XXX: Record constructors and related methods
|
|
// ------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Create a new Oracle-style VARRAY {@link ArrayRecord}
|
|
*/
|
|
static final <R extends ArrayRecord<?>> R newArrayRecord(Class<R> type, Configuration configuration) {
|
|
try {
|
|
return type.getConstructor(Configuration.class).newInstance(configuration);
|
|
}
|
|
catch (Exception e) {
|
|
throw new IllegalStateException(
|
|
"ArrayRecord type does not provide a constructor with signature ArrayRecord(FieldProvider) : " + type
|
|
+ ". Exception : " + e.getMessage());
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new record
|
|
*/
|
|
static final <R extends Record> R newRecord(Class<R> type) {
|
|
return newRecord(type, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new record
|
|
*/
|
|
static final <R extends Record> R newRecord(Class<R> type, Field<?>[] fields) {
|
|
return newRecord(type, fields, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new record
|
|
*/
|
|
static final <R extends Record> R newRecord(Table<R> type) {
|
|
return newRecord(type, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new record
|
|
*/
|
|
static final <R extends Record> R newRecord(Table<R> type, Configuration configuration) {
|
|
return newRecord(type.getRecordType(), type.fields(), configuration);
|
|
}
|
|
|
|
/**
|
|
* Create a new UDT record
|
|
*/
|
|
static final <R extends UDTRecord<R>> R newRecord(UDT<R> type) {
|
|
return newRecord(type, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new UDT record
|
|
*/
|
|
static final <R extends UDTRecord<R>> R newRecord(UDT<R> type, Configuration configuration) {
|
|
return newRecord(type.getRecordType(), type.fields(), configuration);
|
|
}
|
|
|
|
/**
|
|
* Create a new record
|
|
*/
|
|
static final <R extends Record> R newRecord(Class<R> type, Collection<? extends Field<?>> fields, Configuration configuration) {
|
|
return newRecord(type, fieldArray(fields), configuration);
|
|
}
|
|
|
|
/**
|
|
* Create a new record
|
|
*/
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
static final <R extends Record> R newRecord(Class<R> type, Field<?>[] fields, Configuration configuration) {
|
|
try {
|
|
R result;
|
|
|
|
// An ad-hoc type resulting from a JOIN or arbitrary SELECT
|
|
if (type == RecordImpl.class || type == Record.class) {
|
|
result = (R) new RecordImpl(fields);
|
|
}
|
|
|
|
// Any generated record
|
|
else {
|
|
|
|
// [#919] Allow for accessing non-public constructors
|
|
result = Reflect.accessible(type.getDeclaredConstructor()).newInstance();
|
|
}
|
|
|
|
// [#1684] TODO: Do not attach configuration if settings say no
|
|
if (attachRecords(configuration)) {
|
|
result.attach(configuration);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
catch (Exception e) {
|
|
throw new IllegalStateException("Could not construct new record", e);
|
|
}
|
|
}
|
|
|
|
private static final boolean attachRecords(Configuration configuration) {
|
|
if (configuration != null) {
|
|
Settings settings = configuration.getSettings();
|
|
|
|
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<?>[] getClasses(Field<?>[] fields) {
|
|
return getClasses(getDataTypes(fields));
|
|
}
|
|
|
|
/**
|
|
* Useful conversion method
|
|
*/
|
|
static final Class<?>[] getClasses(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<?>[] getClasses(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<?>[] getDataTypes(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<?>[] getDataTypes(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<?>[] getDataTypes(Object[] values) {
|
|
return getDataTypes(getClasses(values));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// XXX: General utility methods
|
|
// ------------------------------------------------------------------------
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
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 <T> Field<T> field(Object value, Field<T> field) {
|
|
|
|
// Fields can be mixed with constant values
|
|
if (value instanceof Field<?>) {
|
|
return (Field<T>) 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;
|
|
}
|
|
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;
|
|
}
|
|
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<Field<?>> fields(Object[] values) {
|
|
List<Field<?>> result = new ArrayList<Field<?>>();
|
|
|
|
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 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 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 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;
|
|
}
|
|
|
|
/**
|
|
* Use this rather than {@link Arrays#asList(Object...)} for
|
|
* <code>null</code>-safety
|
|
*/
|
|
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> map(Record record) {
|
|
Map<Field<?>, Object> result = new LinkedHashMap<Field<?>, 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 <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 InvalidResultException Thrown if the list contains more than one
|
|
* element
|
|
*/
|
|
static final <R extends Record> R filterOne(List<R> 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 <code>null</code>, or throw an
|
|
* exception
|
|
*
|
|
* @param cursor The cursor
|
|
* @return The only element from the cursor or <code>null</code>
|
|
* @throws InvalidResultException Thrown if the cursor returns more than one
|
|
* element
|
|
*/
|
|
static final <R extends Record> R fetchOne(Cursor<R> cursor) throws InvalidResultException {
|
|
R record = cursor.fetchOne();
|
|
|
|
if (cursor.hasNext()) {
|
|
throw new InvalidResultException("Cursor returned more than one result");
|
|
}
|
|
|
|
return record;
|
|
}
|
|
|
|
/**
|
|
* 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>
|
|
*/
|
|
static final void renderAndBind(RenderContext render, BindContext bind, String sql, List<QueryPart> substitutes) {
|
|
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);
|
|
|
|
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 (; sqlChars[i] != '\r' && sqlChars[i] != '\n'; render.sql(sqlChars[i++]));
|
|
|
|
// Consume the newline character
|
|
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 (;;) {
|
|
|
|
// Consume an escaped apostrophe
|
|
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]);
|
|
}
|
|
|
|
// Inline bind variables only outside of string literals
|
|
else if (sqlChars[i] == '?' && substituteIndex < substitutes.size()) {
|
|
QueryPart substitute = substitutes.get(substituteIndex++);
|
|
|
|
if (render.inline()) {
|
|
render.sql(substitute);
|
|
}
|
|
else {
|
|
render.sql(sqlChars[i]);
|
|
}
|
|
|
|
if (bind != null) {
|
|
bind.bind(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 (JDBC_ESCAPE_PATTERN.matcher(sql.substring(i)).matches()) {
|
|
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.sql(substitute);
|
|
|
|
if (bind != null) {
|
|
bind.bind(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]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
char[] peekArray = peek.toCharArray();
|
|
|
|
for (int i = 0; i < peekArray.length; i++) {
|
|
if (index + i >= sqlChars.length) {
|
|
return false;
|
|
}
|
|
if (sqlChars[index + i] != peekArray[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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, Factory.getDataType(type)));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render a list of names of the <code>NamedQueryParts</code> contained in
|
|
* this list.
|
|
*/
|
|
static final void fieldNames(RenderContext 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(RenderContext 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(RenderContext context, Collection<? extends Field<?>> list) {
|
|
String separator = "";
|
|
|
|
for (Field<?> field : list) {
|
|
context.sql(separator).literal(field.getName());
|
|
|
|
separator = ", ";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render a list of names of the <code>NamedQueryParts</code> contained in
|
|
* this list.
|
|
*/
|
|
static final void tableNames(RenderContext 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(RenderContext context, Collection<? extends Table<?>> list) {
|
|
String separator = "";
|
|
|
|
for (Table<?> table : list) {
|
|
context.sql(separator).literal(table.getName());
|
|
|
|
separator = ", ";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
JDBCUtils.safeClose(ctx.resultSet());
|
|
if (!keepStatement)
|
|
JDBCUtils.safeClose(ctx.statement());
|
|
|
|
// [#1868] TODO: This needs to be called in fetchLazy(), too
|
|
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.getValue(sourceField));
|
|
}
|
|
|
|
/**
|
|
* Type-safely set a value to a record
|
|
*/
|
|
static final <T> void setValue(Record target, Field<T> targetField, Object value) {
|
|
target.setValue(targetField, targetField.getDataType().convert(value));
|
|
}
|
|
|
|
/**
|
|
* Map a {@link Schema} according to the configured {@link org.jooq.SchemaMapping}
|
|
*/
|
|
@SuppressWarnings("deprecation")
|
|
static final Schema getMappedSchema(Configuration configuration, Schema schema) {
|
|
org.jooq.SchemaMapping mapping = configuration.getSchemaMapping();
|
|
|
|
if (mapping != null) {
|
|
return mapping.map(schema);
|
|
}
|
|
else {
|
|
return schema;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map a {@link Table} according to the configured {@link org.jooq.SchemaMapping}
|
|
*/
|
|
@SuppressWarnings("deprecation")
|
|
static final Table<?> getMappedTable(Configuration configuration, Table<?> table) {
|
|
org.jooq.SchemaMapping mapping = configuration.getSchemaMapping();
|
|
|
|
if (mapping != null) {
|
|
return mapping.map(table);
|
|
}
|
|
else {
|
|
return table;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a list of ExecuteListener instances (including defaults) from a
|
|
* configuration
|
|
*/
|
|
static final List<ExecuteListener> getListeners(Configuration configuration) {
|
|
List<ExecuteListener> result = new ArrayList<ExecuteListener>();
|
|
|
|
if (!FALSE.equals(configuration.getSettings().isExecuteLogging())) {
|
|
result.add(new StopWatchListener());
|
|
result.add(new LoggerListener());
|
|
}
|
|
|
|
for (ExecuteListener listener : configuration.getExecuteListeners()) {
|
|
result.add(listener);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Wrap a piece of SQL code in parentheses, if not wrapped already
|
|
*/
|
|
static final String wrapInParentheses(String sql) {
|
|
if (sql.startsWith("(")) {
|
|
return sql;
|
|
}
|
|
else {
|
|
return "(" + sql + ")";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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(Object object) {
|
|
return 0x7FFFFFF & object.hashCode();
|
|
}
|
|
|
|
/**
|
|
* Utility method to escape strings or "toString" other objects
|
|
*/
|
|
static final Field<String> escapeForLike(Object value) {
|
|
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
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
static final Field<String> escapeForLike(Field<?> field) {
|
|
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) {
|
|
provider.addConditions(field.equal(record.getValue(field)));
|
|
}
|
|
|
|
/**
|
|
* Extract the configuration from an attachable.
|
|
*/
|
|
static final Configuration getConfiguration(Attachable attachable) {
|
|
if (attachable instanceof AttachableInternal) {
|
|
return ((AttachableInternal) attachable).getConfiguration();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// 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(Class<?> type) {
|
|
if (!isJPAAvailable()) {
|
|
return false;
|
|
}
|
|
|
|
// An @Entity or @Table usually has @Column annotations, too
|
|
if (type.getAnnotation(Entity.class) != null ||
|
|
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;
|
|
}
|
|
}
|
|
|
|
for (Method method : getInstanceMethods(type)) {
|
|
if (method.getAnnotation(Column.class) != null) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get all members annotated with a given column name
|
|
*/
|
|
static final List<java.lang.reflect.Field> getAnnotatedMembers(Class<?> type, String name) {
|
|
List<java.lang.reflect.Field> result = new ArrayList<java.lang.reflect.Field>();
|
|
|
|
for (java.lang.reflect.Field member : getInstanceMembers(type)) {
|
|
Column annotation = member.getAnnotation(Column.class);
|
|
|
|
if (annotation != null) {
|
|
if (name.equals(annotation.name())) {
|
|
result.add(accessible(member));
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get all members matching a given column name
|
|
*/
|
|
static final List<java.lang.reflect.Field> getMatchingMembers(Class<?> type, String name) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Get all setter methods annotated with a given column name
|
|
*/
|
|
static final List<Method> getAnnotatedSetters(Class<?> type, String name) {
|
|
List<Method> result = new ArrayList<Method>();
|
|
|
|
for (Method method : getInstanceMethods(type)) {
|
|
Column annotation = method.getAnnotation(Column.class);
|
|
|
|
if (annotation != null && name.equals(annotation.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();
|
|
|
|
if (m.startsWith("get") || m.startsWith("is")) {
|
|
try {
|
|
Method setter = type.getMethod("set" + m.substring(3), method.getReturnType());
|
|
|
|
// Setter annotation is more relevant
|
|
if (setter.getAnnotation(Column.class) == null) {
|
|
result.add(accessible(setter));
|
|
}
|
|
}
|
|
catch (NoSuchMethodException ignore) {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the first getter method annotated with a given column name
|
|
*/
|
|
static final Method getAnnotatedGetter(Class<?> type, String name) {
|
|
for (Method method : getInstanceMethods(type)) {
|
|
Column annotation = method.getAnnotation(Column.class);
|
|
|
|
if (annotation != null && name.equals(annotation.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;
|
|
}
|
|
|
|
/**
|
|
* Get all setter methods matching a given column name
|
|
*/
|
|
static final List<Method> getMatchingSetters(Class<?> type, String name) {
|
|
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;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the first getter method matching a given column name
|
|
*/
|
|
static final Method getMatchingGetter(Class<?> type, String name) {
|
|
|
|
// [#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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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
|
|
// ------------------------------------------------------------------------
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> T getFromSQLInput(Configuration configuration, SQLInput stream, Field<T> field) throws SQLException {
|
|
Class<T> type = field.getType();
|
|
DataType<T> dataType = field.getDataType();
|
|
|
|
if (type == Blob.class) {
|
|
return (T) stream.readBlob();
|
|
}
|
|
else if (type == Boolean.class) {
|
|
return (T) wasNull(stream, Boolean.valueOf(stream.readBoolean()));
|
|
}
|
|
else if (type == BigInteger.class) {
|
|
BigDecimal result = stream.readBigDecimal();
|
|
return (T) (result == null ? null : result.toBigInteger());
|
|
}
|
|
else if (type == BigDecimal.class) {
|
|
return (T) stream.readBigDecimal();
|
|
}
|
|
else if (type == Byte.class) {
|
|
return (T) wasNull(stream, Byte.valueOf(stream.readByte()));
|
|
}
|
|
else if (type == byte[].class) {
|
|
|
|
// [#1327] Oracle cannot deserialise BLOBs as byte[] from SQLInput
|
|
if (dataType.isLob()) {
|
|
Blob blob = null;
|
|
try {
|
|
blob = stream.readBlob();
|
|
return (T) (blob == null ? null : blob.getBytes(1, (int) blob.length()));
|
|
}
|
|
finally {
|
|
safeFree(blob);
|
|
}
|
|
}
|
|
else {
|
|
return (T) stream.readBytes();
|
|
}
|
|
}
|
|
else if (type == Clob.class) {
|
|
return (T) stream.readClob();
|
|
}
|
|
else if (type == Date.class) {
|
|
return (T) stream.readDate();
|
|
}
|
|
else if (type == Double.class) {
|
|
return (T) wasNull(stream, Double.valueOf(stream.readDouble()));
|
|
}
|
|
else if (type == Float.class) {
|
|
return (T) wasNull(stream, Float.valueOf(stream.readFloat()));
|
|
}
|
|
else if (type == Integer.class) {
|
|
return (T) wasNull(stream, Integer.valueOf(stream.readInt()));
|
|
}
|
|
else if (type == Long.class) {
|
|
return (T) wasNull(stream, Long.valueOf(stream.readLong()));
|
|
}
|
|
else if (type == Short.class) {
|
|
return (T) wasNull(stream, Short.valueOf(stream.readShort()));
|
|
}
|
|
else if (type == String.class) {
|
|
return (T) stream.readString();
|
|
}
|
|
else if (type == Time.class) {
|
|
return (T) stream.readTime();
|
|
}
|
|
else if (type == Timestamp.class) {
|
|
return (T) stream.readTimestamp();
|
|
}
|
|
else if (type == YearToMonth.class) {
|
|
String string = stream.readString();
|
|
return (T) (string == null ? null : YearToMonth.valueOf(string));
|
|
}
|
|
else if (type == DayToSecond.class) {
|
|
String string = stream.readString();
|
|
return (T) (string == null ? null : DayToSecond.valueOf(string));
|
|
}
|
|
else if (type == UByte.class) {
|
|
String string = stream.readString();
|
|
return (T) (string == null ? null : UByte.valueOf(string));
|
|
}
|
|
else if (type == UShort.class) {
|
|
String string = stream.readString();
|
|
return (T) (string == null ? null : UShort.valueOf(string));
|
|
}
|
|
else if (type == UInteger.class) {
|
|
String string = stream.readString();
|
|
return (T) (string == null ? null : UInteger.valueOf(string));
|
|
}
|
|
else if (type == ULong.class) {
|
|
String string = stream.readString();
|
|
return (T) (string == null ? null : ULong.valueOf(string));
|
|
}
|
|
else if (type == UUID.class) {
|
|
return (T) Convert.convert(stream.readString(), UUID.class);
|
|
}
|
|
|
|
// The type byte[] is handled earlier. byte[][] can be handled here
|
|
else if (type.isArray()) {
|
|
Array result = stream.readArray();
|
|
return (T) (result == null ? null : result.getArray());
|
|
}
|
|
else if (ArrayRecord.class.isAssignableFrom(type)) {
|
|
return (T) getArrayRecord(configuration, stream.readArray(), (Class<? extends ArrayRecord<?>>) type);
|
|
}
|
|
else if (EnumType.class.isAssignableFrom(type)) {
|
|
return getEnumType(type, stream.readString());
|
|
}
|
|
else if (UDTRecord.class.isAssignableFrom(type)) {
|
|
return (T) stream.readObject();
|
|
}
|
|
else {
|
|
return (T) stream.readObject();
|
|
}
|
|
}
|
|
|
|
static final <T> void writeToSQLOutput(SQLOutput stream, Field<T> field, T value) throws SQLException {
|
|
Class<T> type = field.getType();
|
|
DataType<T> dataType = field.getDataType();
|
|
|
|
if (value == null) {
|
|
stream.writeObject(null);
|
|
}
|
|
else if (type == Blob.class) {
|
|
stream.writeBlob((Blob) value);
|
|
}
|
|
else if (type == Boolean.class) {
|
|
stream.writeBoolean((Boolean) value);
|
|
}
|
|
else if (type == BigInteger.class) {
|
|
stream.writeBigDecimal(new BigDecimal((BigInteger) value));
|
|
}
|
|
else if (type == BigDecimal.class) {
|
|
stream.writeBigDecimal((BigDecimal) value);
|
|
}
|
|
else if (type == Byte.class) {
|
|
stream.writeByte((Byte) value);
|
|
}
|
|
else if (type == byte[].class) {
|
|
|
|
// [#1327] Oracle cannot serialise BLOBs as byte[] to SQLOutput
|
|
// Use reflection to avoid dependency on OJDBC
|
|
if (dataType.isLob()) {
|
|
Blob blob = null;
|
|
|
|
try {
|
|
blob = on("oracle.sql.BLOB").call("createTemporary",
|
|
on(stream).call("getSTRUCT")
|
|
.call("getJavaSqlConnection").get(),
|
|
false,
|
|
on("oracle.sql.BLOB").get("DURATION_SESSION")).get();
|
|
|
|
blob.setBytes(1, (byte[]) value);
|
|
stream.writeBlob(blob);
|
|
}
|
|
finally {
|
|
DefaultExecuteContext.register(blob);
|
|
}
|
|
}
|
|
else {
|
|
stream.writeBytes((byte[]) value);
|
|
}
|
|
}
|
|
else if (type == Clob.class) {
|
|
stream.writeClob((Clob) value);
|
|
}
|
|
else if (type == Date.class) {
|
|
stream.writeDate((Date) value);
|
|
}
|
|
else if (type == Double.class) {
|
|
stream.writeDouble((Double) value);
|
|
}
|
|
else if (type == Float.class) {
|
|
stream.writeFloat((Float) value);
|
|
}
|
|
else if (type == Integer.class) {
|
|
stream.writeInt((Integer) value);
|
|
}
|
|
else if (type == Long.class) {
|
|
stream.writeLong((Long) value);
|
|
}
|
|
else if (type == Short.class) {
|
|
stream.writeShort((Short) value);
|
|
}
|
|
else if (type == String.class) {
|
|
|
|
// [#1327] Oracle cannot serialise CLOBs as String to SQLOutput
|
|
// Use reflection to avoid dependency on OJDBC
|
|
if (dataType.isLob()) {
|
|
Clob clob = null;
|
|
|
|
try {
|
|
clob = on("oracle.sql.CLOB").call("createTemporary",
|
|
on(stream).call("getSTRUCT")
|
|
.call("getJavaSqlConnection").get(),
|
|
false,
|
|
on("oracle.sql.CLOB").get("DURATION_SESSION")).get();
|
|
|
|
clob.setString(1, (String) value);
|
|
stream.writeClob(clob);
|
|
}
|
|
finally {
|
|
DefaultExecuteContext.register(clob);
|
|
}
|
|
}
|
|
else {
|
|
stream.writeString((String) value);
|
|
}
|
|
}
|
|
else if (type == Time.class) {
|
|
stream.writeTime((Time) value);
|
|
}
|
|
else if (type == Timestamp.class) {
|
|
stream.writeTimestamp((Timestamp) value);
|
|
}
|
|
else if (type == YearToMonth.class) {
|
|
stream.writeString(value.toString());
|
|
}
|
|
else if (type == DayToSecond.class) {
|
|
stream.writeString(value.toString());
|
|
}
|
|
// else if (type.isArray()) {
|
|
// stream.writeArray(value);
|
|
// }
|
|
else if (UNumber.class.isAssignableFrom(type)) {
|
|
stream.writeString(value.toString());
|
|
}
|
|
else if (type == UUID.class) {
|
|
stream.writeString(value.toString());
|
|
}
|
|
else if (ArrayRecord.class.isAssignableFrom(type)) {
|
|
|
|
// [#1544] We can safely assume that localConfiguration has been
|
|
// set on DefaultBindContext, prior to serialising arrays to SQLOut
|
|
Connection connection = DefaultExecuteContext.registeredConfiguration().getConnectionProvider().acquire();
|
|
ArrayRecord<?> arrayRecord = (ArrayRecord<?>) value;
|
|
stream.writeArray(on(connection).call("createARRAY", arrayRecord.getName(), arrayRecord.get()).<Array>get());
|
|
}
|
|
else if (EnumType.class.isAssignableFrom(type)) {
|
|
stream.writeString(((EnumType) value).getLiteral());
|
|
}
|
|
else if (UDTRecord.class.isAssignableFrom(type)) {
|
|
stream.writeObject((UDTRecord<?>) value);
|
|
}
|
|
else {
|
|
throw new UnsupportedOperationException("Type " + type + " is not supported");
|
|
}
|
|
}
|
|
|
|
static final <T, U> U getFromResultSet(ExecuteContext ctx, Field<U> field, int index) throws SQLException {
|
|
|
|
@SuppressWarnings("unchecked")
|
|
Converter<T, U> converter = (Converter<T, U>) DataTypes.converter(field.getType());
|
|
|
|
if (converter != null) {
|
|
return converter.from(getFromResultSet(ctx, converter.fromType(), index));
|
|
}
|
|
else {
|
|
return getFromResultSet(ctx, field.getType(), index);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static final <T> T getFromResultSet(ExecuteContext ctx, Class<T> type, int index) throws SQLException {
|
|
|
|
ResultSet rs = ctx.resultSet();
|
|
|
|
if (type == Blob.class) {
|
|
return (T) rs.getBlob(index);
|
|
}
|
|
else if (type == Boolean.class) {
|
|
return (T) wasNull(rs, Boolean.valueOf(rs.getBoolean(index)));
|
|
}
|
|
else if (type == BigInteger.class) {
|
|
// The SQLite JDBC driver doesn't support BigDecimals
|
|
if (ctx.getDialect() == SQLDialect.SQLITE) {
|
|
return Convert.convert(rs.getString(index), (Class<T>) BigInteger.class);
|
|
}
|
|
else {
|
|
BigDecimal result = rs.getBigDecimal(index);
|
|
return (T) (result == null ? null : result.toBigInteger());
|
|
}
|
|
}
|
|
else if (type == BigDecimal.class) {
|
|
// The SQLite JDBC driver doesn't support BigDecimals
|
|
if (ctx.getDialect() == SQLDialect.SQLITE) {
|
|
return Convert.convert(rs.getString(index), (Class<T>) BigDecimal.class);
|
|
}
|
|
else {
|
|
return (T) rs.getBigDecimal(index);
|
|
}
|
|
}
|
|
else if (type == Byte.class) {
|
|
return (T) wasNull(rs, Byte.valueOf(rs.getByte(index)));
|
|
}
|
|
else if (type == byte[].class) {
|
|
return (T) rs.getBytes(index);
|
|
}
|
|
else if (type == Clob.class) {
|
|
return (T) rs.getClob(index);
|
|
}
|
|
else if (type == Date.class) {
|
|
return (T) getDate(ctx.getDialect(), rs, index);
|
|
}
|
|
else if (type == Double.class) {
|
|
return (T) wasNull(rs, Double.valueOf(rs.getDouble(index)));
|
|
}
|
|
else if (type == Float.class) {
|
|
return (T) wasNull(rs, Float.valueOf(rs.getFloat(index)));
|
|
}
|
|
else if (type == Integer.class) {
|
|
return (T) wasNull(rs, Integer.valueOf(rs.getInt(index)));
|
|
}
|
|
else if (type == Long.class) {
|
|
return (T) wasNull(rs, Long.valueOf(rs.getLong(index)));
|
|
}
|
|
else if (type == Short.class) {
|
|
return (T) wasNull(rs, Short.valueOf(rs.getShort(index)));
|
|
}
|
|
else if (type == String.class) {
|
|
return (T) rs.getString(index);
|
|
}
|
|
else if (type == Time.class) {
|
|
return (T) getTime(ctx.getDialect(), rs, index);
|
|
}
|
|
else if (type == Timestamp.class) {
|
|
return (T) getTimestamp(ctx.getDialect(), rs, index);
|
|
}
|
|
else if (type == YearToMonth.class) {
|
|
if (ctx.getDialect() == POSTGRES) {
|
|
Object object = rs.getObject(index);
|
|
return (T) (object == null ? null : PostgresUtils.toYearToMonth(object));
|
|
}
|
|
else {
|
|
String string = rs.getString(index);
|
|
return (T) (string == null ? null : YearToMonth.valueOf(string));
|
|
}
|
|
}
|
|
else if (type == DayToSecond.class) {
|
|
if (ctx.getDialect() == POSTGRES) {
|
|
Object object = rs.getObject(index);
|
|
return (T) (object == null ? null : PostgresUtils.toDayToSecond(object));
|
|
}
|
|
else {
|
|
String string = rs.getString(index);
|
|
return (T) (string == null ? null : DayToSecond.valueOf(string));
|
|
}
|
|
}
|
|
else if (type == UByte.class) {
|
|
String string = rs.getString(index);
|
|
return (T) (string == null ? null : UByte.valueOf(string));
|
|
}
|
|
else if (type == UShort.class) {
|
|
String string = rs.getString(index);
|
|
return (T) (string == null ? null : UShort.valueOf(string));
|
|
}
|
|
else if (type == UInteger.class) {
|
|
String string = rs.getString(index);
|
|
return (T) (string == null ? null : UInteger.valueOf(string));
|
|
}
|
|
else if (type == ULong.class) {
|
|
String string = rs.getString(index);
|
|
return (T) (string == null ? null : ULong.valueOf(string));
|
|
}
|
|
else if (type == UUID.class) {
|
|
switch (ctx.getDialect()) {
|
|
|
|
// [#1624] Some JDBC drivers natively support the
|
|
// java.util.UUID data type
|
|
case H2:
|
|
case POSTGRES: {
|
|
return (T) rs.getObject(index);
|
|
}
|
|
|
|
// Other SQL dialects deal with UUIDs as if they were CHAR(36)
|
|
// even if they explicitly support them (UNIQUEIDENTIFIER)
|
|
case SQLSERVER:
|
|
case SYBASE:
|
|
|
|
// Most databases don't have such a type. In this case, jOOQ
|
|
// simulates the type
|
|
default: {
|
|
return (T) Convert.convert(rs.getString(index), UUID.class);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The type byte[] is handled earlier. byte[][] can be handled here
|
|
else if (type.isArray()) {
|
|
switch (ctx.getDialect()) {
|
|
case POSTGRES: {
|
|
return pgGetArray(ctx, type, index);
|
|
}
|
|
|
|
default:
|
|
// Note: due to a HSQLDB bug, it is not recommended to call rs.getObject() here:
|
|
// See https://sourceforge.net/tracker/?func=detail&aid=3181365&group_id=23316&atid=378131
|
|
return (T) convertArray(rs.getArray(index), (Class<? extends Object[]>) type);
|
|
}
|
|
}
|
|
else if (ArrayRecord.class.isAssignableFrom(type)) {
|
|
return (T) getArrayRecord(ctx, rs.getArray(index), (Class<? extends ArrayRecord<?>>) type);
|
|
}
|
|
else if (EnumType.class.isAssignableFrom(type)) {
|
|
return getEnumType(type, rs.getString(index));
|
|
}
|
|
else if (UDTRecord.class.isAssignableFrom(type)) {
|
|
switch (ctx.getDialect()) {
|
|
case POSTGRES:
|
|
return (T) pgNewUDTRecord(type, rs.getObject(index));
|
|
}
|
|
|
|
return (T) rs.getObject(index, DataTypes.udtRecords());
|
|
}
|
|
else if (Result.class.isAssignableFrom(type)) {
|
|
ResultSet nested = (ResultSet) rs.getObject(index);
|
|
return (T) new Executor(ctx).fetch(nested);
|
|
}
|
|
else {
|
|
return (T) rs.getObject(index);
|
|
}
|
|
}
|
|
|
|
private static final ArrayRecord<?> getArrayRecord(Configuration configuration, Array array, Class<? extends ArrayRecord<?>> type) throws SQLException {
|
|
if (array == null) {
|
|
return null;
|
|
}
|
|
else {
|
|
// TODO: [#523] Use array record meta data instead
|
|
ArrayRecord<?> record = Utils.newArrayRecord(type, configuration);
|
|
record.set(array);
|
|
return record;
|
|
}
|
|
}
|
|
|
|
private static final Object[] convertArray(Object array, Class<? extends Object[]> type) throws SQLException {
|
|
if (array instanceof Object[]) {
|
|
return Convert.convert(array, type);
|
|
}
|
|
else if (array instanceof Array) {
|
|
return convertArray((Array) array, type);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static final Object[] convertArray(Array array, Class<? extends Object[]> type) throws SQLException {
|
|
if (array != null) {
|
|
return Convert.convert(array.getArray(), type);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static final Date getDate(SQLDialect dialect, ResultSet rs, int index) throws SQLException {
|
|
|
|
// SQLite's type affinity needs special care...
|
|
if (dialect == SQLDialect.SQLITE) {
|
|
String date = rs.getString(index);
|
|
|
|
if (date != null) {
|
|
return new Date(parse("yyyy-MM-dd", date));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Cubrid SQL dates are incorrectly fetched. Reset milliseconds...
|
|
// See http://jira.cubrid.org/browse/APIS-159
|
|
// See https://sourceforge.net/apps/trac/cubridinterface/ticket/140
|
|
else if (dialect == CUBRID) {
|
|
Date date = rs.getDate(index);
|
|
|
|
if (date != null) {
|
|
Calendar cal = Calendar.getInstance();
|
|
cal.setTimeInMillis(date.getTime());
|
|
cal.set(Calendar.MILLISECOND, 0);
|
|
date = new Date(cal.getTimeInMillis());
|
|
}
|
|
|
|
return date;
|
|
}
|
|
|
|
else {
|
|
return rs.getDate(index);
|
|
}
|
|
}
|
|
|
|
private static final Time getTime(SQLDialect dialect, ResultSet rs, int index) throws SQLException {
|
|
|
|
// SQLite's type affinity needs special care...
|
|
if (dialect == SQLDialect.SQLITE) {
|
|
String time = rs.getString(index);
|
|
|
|
if (time != null) {
|
|
return new Time(parse("HH:mm:ss", time));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Cubrid SQL dates are incorrectly fetched. Reset milliseconds...
|
|
// See http://jira.cubrid.org/browse/APIS-159
|
|
// See https://sourceforge.net/apps/trac/cubridinterface/ticket/140
|
|
else if (dialect == CUBRID) {
|
|
Time time = rs.getTime(index);
|
|
|
|
if (time != null) {
|
|
Calendar cal = Calendar.getInstance();
|
|
cal.setTimeInMillis(time.getTime());
|
|
cal.set(Calendar.MILLISECOND, 0);
|
|
time = new Time(cal.getTimeInMillis());
|
|
}
|
|
|
|
return time;
|
|
}
|
|
|
|
else {
|
|
return rs.getTime(index);
|
|
}
|
|
}
|
|
|
|
private static final Timestamp getTimestamp(SQLDialect dialect, ResultSet rs, int index) throws SQLException {
|
|
|
|
// SQLite's type affinity needs special care...
|
|
if (dialect == SQLDialect.SQLITE) {
|
|
String timestamp = rs.getString(index);
|
|
|
|
if (timestamp != null) {
|
|
return new Timestamp(parse("yyyy-MM-dd HH:mm:ss", timestamp));
|
|
}
|
|
|
|
return null;
|
|
} else {
|
|
return rs.getTimestamp(index);
|
|
}
|
|
}
|
|
|
|
private static final long parse(String pattern, String date) throws SQLException {
|
|
try {
|
|
|
|
// Try reading a plain number first
|
|
try {
|
|
return Long.valueOf(date);
|
|
}
|
|
|
|
// If that fails, try reading a formatted date
|
|
catch (NumberFormatException e) {
|
|
return new SimpleDateFormat(pattern).parse(date).getTime();
|
|
}
|
|
}
|
|
catch (ParseException e) {
|
|
throw new SQLException("Could not parse date " + date, e);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static final <T> T getEnumType(Class<T> type, String literal) throws SQLException {
|
|
try {
|
|
Object[] list = (Object[]) type.getMethod("values").invoke(type);
|
|
|
|
for (Object e : list) {
|
|
String l = ((EnumType) e).getLiteral();
|
|
|
|
if (l.equals(literal)) {
|
|
return (T) e;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
throw new SQLException("Unknown enum literal found : " + literal);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> T getFromStatement(ExecuteContext ctx, Class<T> type, int index) throws SQLException {
|
|
CallableStatement stmt = (CallableStatement) ctx.statement();
|
|
|
|
if (type == Blob.class) {
|
|
return (T) stmt.getBlob(index);
|
|
}
|
|
else if (type == Boolean.class) {
|
|
return (T) wasNull(stmt, Boolean.valueOf(stmt.getBoolean(index)));
|
|
}
|
|
else if (type == BigInteger.class) {
|
|
BigDecimal result = stmt.getBigDecimal(index);
|
|
return (T) (result == null ? null : result.toBigInteger());
|
|
}
|
|
else if (type == BigDecimal.class) {
|
|
return (T) stmt.getBigDecimal(index);
|
|
}
|
|
else if (type == Byte.class) {
|
|
return (T) wasNull(stmt, Byte.valueOf(stmt.getByte(index)));
|
|
}
|
|
else if (type == byte[].class) {
|
|
return (T) stmt.getBytes(index);
|
|
}
|
|
else if (type == Clob.class) {
|
|
return (T) stmt.getClob(index);
|
|
}
|
|
else if (type == Date.class) {
|
|
return (T) stmt.getDate(index);
|
|
}
|
|
else if (type == Double.class) {
|
|
return (T) wasNull(stmt, Double.valueOf(stmt.getDouble(index)));
|
|
}
|
|
else if (type == Float.class) {
|
|
return (T) wasNull(stmt, Float.valueOf(stmt.getFloat(index)));
|
|
}
|
|
else if (type == Integer.class) {
|
|
return (T) wasNull(stmt, Integer.valueOf(stmt.getInt(index)));
|
|
}
|
|
else if (type == Long.class) {
|
|
return (T) wasNull(stmt, Long.valueOf(stmt.getLong(index)));
|
|
}
|
|
else if (type == Short.class) {
|
|
return (T) wasNull(stmt, Short.valueOf(stmt.getShort(index)));
|
|
}
|
|
else if (type == String.class) {
|
|
return (T) stmt.getString(index);
|
|
}
|
|
else if (type == Time.class) {
|
|
return (T) stmt.getTime(index);
|
|
}
|
|
else if (type == Timestamp.class) {
|
|
return (T) stmt.getTimestamp(index);
|
|
}
|
|
else if (type == YearToMonth.class) {
|
|
if (ctx.getDialect() == POSTGRES) {
|
|
Object object = stmt.getObject(index);
|
|
return (T) (object == null ? null : PostgresUtils.toYearToMonth(object));
|
|
}
|
|
else {
|
|
String string = stmt.getString(index);
|
|
return (T) (string == null ? null : YearToMonth.valueOf(string));
|
|
}
|
|
}
|
|
else if (type == DayToSecond.class) {
|
|
if (ctx.getDialect() == POSTGRES) {
|
|
Object object = stmt.getObject(index);
|
|
return (T) (object == null ? null : PostgresUtils.toDayToSecond(object));
|
|
}
|
|
else {
|
|
String string = stmt.getString(index);
|
|
return (T) (string == null ? null : DayToSecond.valueOf(string));
|
|
}
|
|
}
|
|
else if (type == UByte.class) {
|
|
String string = stmt.getString(index);
|
|
return (T) (string == null ? null : UByte.valueOf(string));
|
|
}
|
|
else if (type == UShort.class) {
|
|
String string = stmt.getString(index);
|
|
return (T) (string == null ? null : UShort.valueOf(string));
|
|
}
|
|
else if (type == UInteger.class) {
|
|
String string = stmt.getString(index);
|
|
return (T) (string == null ? null : UInteger.valueOf(string));
|
|
}
|
|
else if (type == ULong.class) {
|
|
String string = stmt.getString(index);
|
|
return (T) (string == null ? null : ULong.valueOf(string));
|
|
}
|
|
else if (type == UUID.class) {
|
|
switch (ctx.getDialect()) {
|
|
|
|
// [#1624] Some JDBC drivers natively support the
|
|
// java.util.UUID data type
|
|
case H2:
|
|
case POSTGRES: {
|
|
return (T) stmt.getObject(index);
|
|
}
|
|
|
|
// Other SQL dialects deal with UUIDs as if they were CHAR(36)
|
|
// even if they explicitly support them (UNIQUEIDENTIFIER)
|
|
case SQLSERVER:
|
|
case SYBASE:
|
|
|
|
// Most databases don't have such a type. In this case, jOOQ
|
|
// simulates the type
|
|
default: {
|
|
return (T) Convert.convert(stmt.getString(index), UUID.class);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The type byte[] is handled earlier. byte[][] can be handled here
|
|
else if (type.isArray()) {
|
|
return (T) convertArray(stmt.getObject(index), (Class<? extends Object[]>)type);
|
|
}
|
|
else if (ArrayRecord.class.isAssignableFrom(type)) {
|
|
return (T) getArrayRecord(ctx, stmt.getArray(index), (Class<? extends ArrayRecord<?>>) type);
|
|
}
|
|
else if (EnumType.class.isAssignableFrom(type)) {
|
|
return getEnumType(type, stmt.getString(index));
|
|
}
|
|
else if (UDTRecord.class.isAssignableFrom(type)) {
|
|
switch (ctx.getDialect()) {
|
|
case POSTGRES:
|
|
return (T) pgNewUDTRecord(type, stmt.getObject(index));
|
|
}
|
|
|
|
return (T) stmt.getObject(index, DataTypes.udtRecords());
|
|
}
|
|
else if (Result.class.isAssignableFrom(type)) {
|
|
ResultSet nested = (ResultSet) stmt.getObject(index);
|
|
return (T) new Executor(ctx).fetch(nested);
|
|
}
|
|
else {
|
|
return (T) stmt.getObject(index);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// XXX: The following section has been added for Postgres UDT support. The
|
|
// official Postgres JDBC driver does not implement SQLData and similar
|
|
// interfaces. Instead, a string representation of a UDT has to be parsed
|
|
// -------------------------------------------------------------------------
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static final <T> T pgFromString(Class<T> type, String string) throws SQLException {
|
|
if (string == null) {
|
|
return null;
|
|
}
|
|
else if (type == Blob.class) {
|
|
// Not supported
|
|
}
|
|
else if (type == Boolean.class) {
|
|
return (T) Boolean.valueOf(string);
|
|
}
|
|
else if (type == BigInteger.class) {
|
|
return (T) new BigInteger(string);
|
|
}
|
|
else if (type == BigDecimal.class) {
|
|
return (T) new BigDecimal(string);
|
|
}
|
|
else if (type == Byte.class) {
|
|
return (T) Byte.valueOf(string);
|
|
}
|
|
else if (type == byte[].class) {
|
|
return (T) PostgresUtils.toBytes(string);
|
|
}
|
|
else if (type == Clob.class) {
|
|
// Not supported
|
|
}
|
|
else if (type == Date.class) {
|
|
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
|
|
return (T) new Date(pgParseDate(string, f).getTime());
|
|
}
|
|
else if (type == Double.class) {
|
|
return (T) Double.valueOf(string);
|
|
}
|
|
else if (type == Float.class) {
|
|
return (T) Float.valueOf(string);
|
|
}
|
|
else if (type == Integer.class) {
|
|
return (T) Integer.valueOf(string);
|
|
}
|
|
else if (type == Long.class) {
|
|
return (T) Long.valueOf(string);
|
|
}
|
|
else if (type == Short.class) {
|
|
return (T) Short.valueOf(string);
|
|
}
|
|
else if (type == String.class) {
|
|
return (T) string;
|
|
}
|
|
else if (type == Time.class) {
|
|
SimpleDateFormat f = new SimpleDateFormat("HH:mm:ss");
|
|
return (T) new Time(pgParseDate(string, f).getTime());
|
|
}
|
|
else if (type == Timestamp.class) {
|
|
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
return (T) new Timestamp(pgParseDate(string, f).getTime());
|
|
}
|
|
else if (type == UByte.class) {
|
|
return (T) UByte.valueOf(string);
|
|
}
|
|
else if (type == UShort.class) {
|
|
return (T) UShort.valueOf(string);
|
|
}
|
|
else if (type == UInteger.class) {
|
|
return (T) UInteger.valueOf(string);
|
|
}
|
|
else if (type == ULong.class) {
|
|
return (T) ULong.valueOf(string);
|
|
}
|
|
else if (type.isArray()) {
|
|
return (T) pgNewArray(type, string);
|
|
}
|
|
else if (ArrayRecord.class.isAssignableFrom(type)) {
|
|
// Not supported
|
|
}
|
|
else if (EnumType.class.isAssignableFrom(type)) {
|
|
return getEnumType(type, string);
|
|
}
|
|
else if (UDTRecord.class.isAssignableFrom(type)) {
|
|
return (T) pgNewUDTRecord(type, string);
|
|
}
|
|
|
|
throw new UnsupportedOperationException("Class " + type + " is not supported");
|
|
}
|
|
|
|
private static final java.util.Date pgParseDate(String string, SimpleDateFormat f) throws SQLException {
|
|
try {
|
|
return f.parse(string);
|
|
}
|
|
catch (ParseException e) {
|
|
throw new SQLException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a UDT record from a PGobject
|
|
* <p>
|
|
* Unfortunately, this feature is very poorly documented and true UDT
|
|
* support by the PostGreSQL JDBC driver has been postponed for a long time.
|
|
*
|
|
* @param object An object of type PGobject. The actual argument type cannot
|
|
* be expressed in the method signature, as no explicit
|
|
* dependency to postgres logic is desired
|
|
* @return The converted {@link UDTRecord}
|
|
*/
|
|
private static final UDTRecord<?> pgNewUDTRecord(Class<?> type, Object object) throws SQLException {
|
|
if (object == null) {
|
|
return null;
|
|
}
|
|
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
UDTRecord<?> record = (UDTRecord<?>) Utils.newRecord((Class) type);
|
|
List<String> values = PostgresUtils.toPGObject(object.toString());
|
|
|
|
Row row = record.fieldsRow();
|
|
for (int i = 0; i < row.size(); i++) {
|
|
pgSetValue(record, row.field(i), values.get(i));
|
|
}
|
|
|
|
return record;
|
|
}
|
|
|
|
/**
|
|
* Workarounds for the unimplemented Postgres JDBC driver features
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
private static final <T> T pgGetArray(ExecuteContext ctx, Class<T> type, int index) throws SQLException {
|
|
|
|
ResultSet rs = ctx.resultSet();
|
|
|
|
// Get the JDBC Array and check for null. If null, that's OK
|
|
Array array = rs.getArray(index);
|
|
if (array == null) {
|
|
return null;
|
|
}
|
|
|
|
// Try fetching a Java Object[]. That's gonna work for non-UDT types
|
|
try {
|
|
return (T) convertArray(rs.getArray(index), (Class<? extends Object[]>) type);
|
|
}
|
|
|
|
// This might be a UDT (not implemented exception...)
|
|
catch (Exception e) {
|
|
List<Object> result = new ArrayList<Object>();
|
|
|
|
// Try fetching the array as a JDBC ResultSet
|
|
try {
|
|
ctx.resultSet(array.getResultSet());
|
|
while (ctx.resultSet().next()) {
|
|
result.add(getFromResultSet(ctx, type.getComponentType(), 2));
|
|
}
|
|
}
|
|
|
|
// That might fail too, then we don't know any further...
|
|
catch (Exception fatal) {
|
|
log.error("Cannot parse Postgres array: " + rs.getString(index));
|
|
log.error(fatal);
|
|
return null;
|
|
}
|
|
|
|
finally {
|
|
ctx.resultSet(rs);
|
|
}
|
|
|
|
return (T) convertArray(result.toArray(), (Class<? extends Object[]>) type);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create an array from a String
|
|
* <p>
|
|
* Unfortunately, this feature is very poorly documented and true UDT
|
|
* support by the PostGreSQL JDBC driver has been postponed for a long time.
|
|
*
|
|
* @param string A String representation of an array
|
|
* @return The converted array
|
|
*/
|
|
private static final Object[] pgNewArray(Class<?> type, String string) throws SQLException {
|
|
if (string == null) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
Class<?> component = type.getComponentType();
|
|
String values = string.replaceAll("^\\{(.*)\\}$", "$1");
|
|
|
|
if ("".equals(values)) {
|
|
return (Object[]) java.lang.reflect.Array.newInstance(component, 0);
|
|
}
|
|
else {
|
|
String[] split = values.split(",");
|
|
Object[] result = (Object[]) java.lang.reflect.Array.newInstance(component, split.length);
|
|
|
|
for (int i = 0; i < split.length; i++) {
|
|
result[i] = pgFromString(type.getComponentType(), split[i]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
throw new SQLException(e);
|
|
}
|
|
}
|
|
|
|
private static final <T> void pgSetValue(UDTRecord<?> record, Field<T> field, String value) throws SQLException {
|
|
record.setValue(field, pgFromString(field.getType(), value));
|
|
}
|
|
} |