org.jooq.impl.DefaultDataType Maven / Gradle / Ivy
/**
* 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.util.Collections.unmodifiableCollection;
import static org.jooq.impl.SQLDataType.BLOB;
import static org.jooq.impl.SQLDataType.CLOB;
import static org.jooq.impl.SQLDataType.NCLOB;
import static org.jooq.tools.reflect.Reflect.wrapper;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
// ...
import org.jooq.Binding;
import org.jooq.Configuration;
import org.jooq.Converter;
import org.jooq.Converters;
import org.jooq.DataType;
import org.jooq.EnumType;
import org.jooq.Field;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.UDTRecord;
import org.jooq.exception.MappingException;
import org.jooq.exception.SQLDialectNotSupportedException;
import org.jooq.tools.Convert;
import org.jooq.types.Interval;
import org.jooq.types.UByte;
import org.jooq.types.UInteger;
import org.jooq.types.ULong;
import org.jooq.types.UShort;
/**
* A common base class for data types.
*
* This also acts as a static data type registry for jOOQ internally.
*
* This type is for JOOQ INTERNAL USE only. Do not reference directly
*
* @author Lukas Eder
*/
@SuppressWarnings({"unchecked"})
public class DefaultDataType implements DataType {
/**
* Generated UID
*/
private static final long serialVersionUID = 4155588654449505119L;
/**
* A pattern for data type name normalisation.
*/
private static final Pattern NORMALISE_PATTERN = Pattern.compile("\"|\\.|\\s|\\(\\w+(\\s*,\\s*\\w+)*\\)|(NOT\\s*NULL)?");
/**
* A pattern to be used to replace all precision, scale, and length
* information.
*/
private static final Pattern TYPE_NAME_PATTERN = Pattern.compile("\\([^\\)]*\\)");
// -------------------------------------------------------------------------
// Data type caches
// -------------------------------------------------------------------------
/**
* A cache for dialect-specific data types by normalised.
*/
private static final Map>[] TYPES_BY_NAME;
/**
* A cache for dialect-specific data types by Java type.
*/
private static final Map, DataType>[] TYPES_BY_TYPE;
/**
* A cache for dialect-specific data types by SQL DataTypes.
*/
private static final Map, DataType>[] TYPES_BY_SQL_DATATYPE;
/**
* A cache for SQL DataTypes by Java type.
*/
private static final Map, DataType> SQL_DATATYPES_BY_TYPE;
// -------------------------------------------------------------------------
// Precisions
// -------------------------------------------------------------------------
/**
* The minimum decimal precision needed to represent a Java {@link Long}
* type.
*/
private static final int LONG_PRECISION = String.valueOf(Long.MAX_VALUE).length();
/**
* The minimum decimal precision needed to represent a Java {@link Integer}
* type.
*/
private static final int INTEGER_PRECISION = String.valueOf(Integer.MAX_VALUE).length();
/**
* The minimum decimal precision needed to represent a Java {@link Short}
* type.
*/
private static final int SHORT_PRECISION = String.valueOf(Short.MAX_VALUE).length();
/**
* The minimum decimal precision needed to represent a Java {@link Byte}
* type.
*/
private static final int BYTE_PRECISION = String.valueOf(Byte.MAX_VALUE).length();
// -------------------------------------------------------------------------
// Data type attributes
// -------------------------------------------------------------------------
/**
* The SQL dialect associated with this data type.
*/
private final SQLDialect dialect;
/**
* The SQL DataType corresponding to this data type.
*/
private final DataType sqlDataType;
/**
* The Java class corresponding to this data type.
*/
private final Class type;
/**
* The data type binding corresponding to this data type.
*/
private final Binding binding;
/**
* The Java class corresponding to arrays of this data type.
*/
private final Class arrayType;
/**
* The type name used for casting to this type.
*/
private final String castTypeName;
/**
* The type name used for casting to this type.
*/
private final String castTypeBase;
/**
* The type name.
*/
private final String typeName;
private final boolean nullable;
private final Field defaultValue;
private final int precision;
private final int scale;
private final int length;
static {
TYPES_BY_SQL_DATATYPE = new Map[SQLDialect.values().length];
TYPES_BY_NAME = new Map[SQLDialect.values().length];
TYPES_BY_TYPE = new Map[SQLDialect.values().length];
for (SQLDialect dialect : SQLDialect.values()) {
TYPES_BY_SQL_DATATYPE[dialect.ordinal()] = new LinkedHashMap, DataType>();
TYPES_BY_NAME[dialect.ordinal()] = new LinkedHashMap>();
TYPES_BY_TYPE[dialect.ordinal()] = new LinkedHashMap, DataType>();
}
SQL_DATATYPES_BY_TYPE = new LinkedHashMap, DataType>();
// [#2506] Transitively load all dialect-specific data types
try {
Class.forName(SQLDataType.class.getName());
}
catch (Exception ignore) {}
}
public DefaultDataType(SQLDialect dialect, DataType sqlDataType, String typeName) {
this(dialect, sqlDataType, sqlDataType.getType(), typeName, typeName, sqlDataType.precision(), sqlDataType.scale(), sqlDataType.length(), sqlDataType.nullable(), sqlDataType.defaultValue());
}
public DefaultDataType(SQLDialect dialect, DataType sqlDataType, String typeName, String castTypeName) {
this(dialect, sqlDataType, sqlDataType.getType(), typeName, castTypeName, sqlDataType.precision(), sqlDataType.scale(), sqlDataType.length(), sqlDataType.nullable(), sqlDataType.defaultValue());
}
public DefaultDataType(SQLDialect dialect, Class type, String typeName) {
this(dialect, null, type, typeName, typeName, 0, 0, 0, true, null);
}
public DefaultDataType(SQLDialect dialect, Class type, String typeName, String castTypeName) {
this(dialect, null, type, typeName, castTypeName, 0, 0, 0, true, null);
}
DefaultDataType(SQLDialect dialect, Class type, String typeName, String castTypeName, int precision, int scale, int length, boolean nullable, Field defaultValue) {
this(dialect, null, type, typeName, castTypeName, precision, scale, length, nullable, defaultValue);
}
DefaultDataType(SQLDialect dialect, Class type, Binding binding, String typeName, String castTypeName, int precision, int scale, int length, boolean nullable, Field defaultValue) {
this(dialect, null, type, binding, typeName, castTypeName, precision, scale, length, nullable, defaultValue);
}
DefaultDataType(SQLDialect dialect, DataType sqlDataType, Class type, String typeName, String castTypeName, int precision, int scale, int length, boolean nullable, Field defaultValue) {
this(dialect, sqlDataType, type, null, typeName, castTypeName, precision, scale, length, nullable, defaultValue);
}
DefaultDataType(SQLDialect dialect, DataType sqlDataType, Class type, Binding binding, String typeName, String castTypeName, int precision, int scale, int length, boolean nullable, Field defaultValue) {
// Initialise final instance members
// ---------------------------------
this.dialect = dialect;
// [#858] SQLDataTypes should reference themselves for more convenience
this.sqlDataType = (dialect == null) ? this : sqlDataType;
this.type = type;
this.typeName = typeName;
this.castTypeName = castTypeName;
this.castTypeBase = TYPE_NAME_PATTERN.matcher(castTypeName).replaceAll("").trim();
this.arrayType = (Class) Array.newInstance(type, 0).getClass();
this.nullable = nullable;
this.defaultValue = defaultValue;
this.precision = precision0(type, precision);
this.scale = scale;
this.length = length;
// Register data type in static caches
// -----------------------------------
// Dialect-specific data types
int ordinal = dialect == null ? SQLDialect.DEFAULT.ordinal() : dialect.family().ordinal();
// [#3225] Avoid normalisation if not necessary
if (!TYPES_BY_NAME[ordinal].containsKey(typeName.toUpperCase())) {
String normalised = DefaultDataType.normalise(typeName);
if (TYPES_BY_NAME[ordinal].get(normalised) == null) {
TYPES_BY_NAME[ordinal].put(normalised, this);
}
}
if (TYPES_BY_TYPE[ordinal].get(type) == null) {
TYPES_BY_TYPE[ordinal].put(type, this);
}
if (TYPES_BY_SQL_DATATYPE[ordinal].get(sqlDataType) == null) {
TYPES_BY_SQL_DATATYPE[ordinal].put(sqlDataType, this);
}
// Global data types
if (dialect == null) {
if (SQL_DATATYPES_BY_TYPE.get(type) == null) {
SQL_DATATYPES_BY_TYPE.put(type, this);
}
}
this.binding = binding != null
? binding
: new DefaultBinding(Converters.identity(type), this.isLob());
}
/**
* [#3225] Performant constructor for creating derived types.
*/
private DefaultDataType(DefaultDataType t, int precision, int scale, int length, boolean nullable, Field defaultValue) {
this.dialect = t.dialect;
this.sqlDataType = t.sqlDataType;
this.type = t.type;
this.typeName = t.typeName;
this.castTypeName = t.castTypeName;
this.castTypeBase = t.castTypeBase;
this.arrayType = t.arrayType;
this.nullable = nullable;
this.defaultValue = defaultValue;
this.precision = precision0(type, precision);
this.scale = scale;
this.length = length;
this.binding = t.binding;
}
private static final int precision0(Class type, int precision) {
if (precision == 0) {
if (type == Long.class || type == ULong.class) {
precision = LONG_PRECISION;
}
else if (type == Integer.class || type == UInteger.class) {
precision = INTEGER_PRECISION;
}
else if (type == Short.class || type == UShort.class) {
precision = SHORT_PRECISION;
}
else if (type == Byte.class || type == UByte.class) {
precision = BYTE_PRECISION;
}
}
return precision;
}
@Override
public final DataType nullable(boolean n) {
return new DefaultDataType(this, precision, scale, length, n, defaultValue);
}
@Override
public final boolean nullable() {
return nullable;
}
@Override
public final DataType defaultValue(T d) {
return defaultValue(Tools.field(d, this));
}
@Override
public final DataType defaultValue(Field d) {
return new DefaultDataType(this, precision, scale, length, nullable, d);
}
@Override
public final Field defaultValue() {
return defaultValue;
}
@Override
@Deprecated
public final DataType defaulted(boolean d) {
return defaultValue(d ? Tools.field(null, this) : null);
}
@Override
public final boolean defaulted() {
return defaultValue != null;
}
@Override
public final DataType precision(int p) {
return precision(p, scale);
}
@Override
public final DataType precision(int p, int s) {
if (precision == p && scale == s)
return this;
// [#4120] LOB types are not allowed to have precision
else if (isLob())
return this;
else
return new DefaultDataType(this, p, s, length, nullable, defaultValue);
}
@Override
public final int precision() {
return precision;
}
@Override
public final boolean hasPrecision() {
return type == BigInteger.class || type == BigDecimal.class;
}
@Override
public final DataType scale(int s) {
if (scale == s)
return this;
// [#4120] LOB types are not allowed to have scale
if (isLob())
return this;
else
return new DefaultDataType(this, precision, s, length, nullable, defaultValue);
}
@Override
public final int scale() {
return scale;
}
@Override
public final boolean hasScale() {
return type == BigDecimal.class;
}
@Override
public final DataType length(int l) {
if (length == l)
return this;
// [#4120] LOB types are not allowed to have length
if (isLob())
return this;
else
return new DefaultDataType(this, precision, scale, l, nullable, defaultValue);
}
@Override
public final int length() {
return length;
}
@Override
public final boolean hasLength() {
return (type == byte[].class || type == String.class) && !isLob();
}
@Override
public final DataType getSQLDataType() {
return sqlDataType;
}
@Override
public final DataType getDataType(Configuration configuration) {
// If this is a SQLDataType find the most suited dialect-specific
// data type
if (getDialect() == null) {
DataType dataType = TYPES_BY_SQL_DATATYPE[configuration.dialect().family().ordinal()]
// Be sure to reset length, precision, and scale, as those values
// were not registered in the below cache
.get(length(0).precision(0, 0));
if (dataType != null) {
// ... and then, set them back to the original value
// [#2710] TODO: Remove this logic along with cached data types
return (DataType) dataType.length(length).precision(precision, scale);
}
}
// If this is already the dialect's specific data type, return this
else if (getDialect().family() == configuration.dialect().family()) {
return this;
}
// If the SQL data type is not available stick with this data type
else if (getSQLDataType() == null) {
return this;
}
// If this is another dialect's specific data type, recurse
else {
getSQLDataType().getDataType(configuration);
}
return this;
}
@Override
public /* final */ int getSQLType() {
// TODO [#1227] There is some confusion with these types, especially
// when it comes to byte[] which can be mapped to BLOB, BINARY, VARBINARY
if (type == Blob.class) {
return Types.BLOB;
}
else if (type == Boolean.class) {
return Types.BOOLEAN;
}
else if (type == BigInteger.class) {
return Types.BIGINT;
}
else if (type == BigDecimal.class) {
return Types.DECIMAL;
}
else if (type == Byte.class) {
return Types.TINYINT;
}
else if (type == byte[].class) {
return Types.BLOB;
}
else if (type == Clob.class) {
return Types.CLOB;
}
else if (type == Date.class) {
return Types.DATE;
}
else if (type == Double.class) {
return Types.DOUBLE;
}
else if (type == Float.class) {
return Types.FLOAT;
}
else if (type == Integer.class) {
return Types.INTEGER;
}
else if (type == Long.class) {
return Types.BIGINT;
}
else if (type == Short.class) {
return Types.SMALLINT;
}
else if (type == String.class) {
return Types.VARCHAR;
}
else if (type == Time.class) {
return Types.TIME;
}
else if (type == Timestamp.class) {
return Types.TIMESTAMP;
}
// The type byte[] is handled earlier.
else if (type.isArray()) {
return Types.ARRAY;
}
else if (EnumType.class.isAssignableFrom(type)) {
return Types.VARCHAR;
}
else if (UDTRecord.class.isAssignableFrom(type)) {
return Types.STRUCT;
}
else if (Result.class.isAssignableFrom(type)) {
switch (dialect.family()) {
case H2:
return -10; // OracleTypes.CURSOR;
case POSTGRES:
default:
return Types.OTHER;
}
}
else {
return Types.OTHER;
}
}
@Override
public final Class getType() {
return type;
}
@Override
public final Binding getBinding() {
return binding;
}
@Override
public final Converter getConverter() {
return binding.converter();
}
@Override
public final Class getArrayType() {
return arrayType;
}
@Override
public final String getTypeName() {
return typeName;
}
@Override
public String getTypeName(Configuration configuration) {
return getDataType(configuration).getTypeName();
}
@Override
public final String getCastTypeName() {
if (length != 0 && hasLength()) {
return castTypeBase + "(" + length + ")";
}
else if (precision != 0 && hasPrecision()) {
if (scale != 0 && hasScale()) {
return castTypeBase + "(" + precision + ", " + scale + ")";
}
else {
return castTypeBase + "(" + precision + ")";
}
}
else {
return castTypeName;
}
}
@Override
public String getCastTypeName(Configuration configuration) {
return getDataType(configuration).getCastTypeName();
}
@Override
public final DataType getArrayDataType() {
return new ArrayDataType(this);
}
@Override
public final DataType asEnumDataType(Class enumDataType) {
String enumTypeName = Tools.enums(enumDataType)[0].getName();
return new DefaultDataType(dialect, enumDataType, enumTypeName, enumTypeName);
}
@Override
public final DataType asConvertedDataType(Converter converter) {
return asConvertedDataType(DefaultBinding.newBinding(converter, this, null));
}
@SuppressWarnings("deprecation")
@Override
public final DataType asConvertedDataType(Binding newBinding) {
if (binding == newBinding)
return (DataType) this;
if (newBinding == null)
newBinding = (Binding) new DefaultBinding(Converters.identity(getType()), isLob());
return new ConvertedDataType(this, newBinding);
}
@Override
public final SQLDialect getDialect() {
return dialect;
}
@Override
public /* final */ T convert(Object object) {
// [#1441] Avoid unneeded type conversions to improve performance
if (object == null) {
return null;
}
else if (object.getClass() == type) {
return (T) object;
}
else {
return Convert.convert(object, type);
}
}
@Override
public final T[] convert(Object... objects) {
return (T[]) Convert.convertArray(objects, type);
}
@Override
public final List convert(Collection objects) {
return Convert.convert(objects, type);
}
public static DataType