/* * 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.Types; import java.time.OffsetDateTime; import java.time.OffsetTime; 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. * <p> * This also acts as a static data type registry for jOOQ internally. * <p> * This type is for JOOQ INTERNAL USE only. Do not reference directly * * @author Lukas Eder */ @SuppressWarnings({"unchecked"}) public class DefaultDataType<T> implements DataType<T> { /** * 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<String, DataType<?>>[] TYPES_BY_NAME; /** * A cache for dialect-specific data types by Java type. */ private static final Map<Class<?>, DataType<?>>[] TYPES_BY_TYPE; /** * A cache for dialect-specific data types by SQL DataTypes. */ private static final Map<DataType<?>, DataType<?>>[] TYPES_BY_SQL_DATATYPE; /** * A cache for SQL DataTypes by Java type. */ private static final Map<Class<?>, 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<T> sqlDataType; /** * The Java class corresponding to this data type. */ private final Class<T> type; /** * The data type binding corresponding to this data type. */ private final Binding<?, T> binding; /** * The Java class corresponding to arrays of this data type. */ private final Class<T[]> 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 boolean identity; private final Field<T> 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<?>, DataType<?>>(); TYPES_BY_NAME[dialect.ordinal()] = new LinkedHashMap<String, DataType<?>>(); TYPES_BY_TYPE[dialect.ordinal()] = new LinkedHashMap<Class<?>, DataType<?>>(); } SQL_DATATYPES_BY_TYPE = new LinkedHashMap<Class<?>, DataType<?>>(); // [#2506] Transitively load all dialect-specific data types try { Class.forName(SQLDataType.class.getName()); } catch (Exception ignore) {} } public DefaultDataType(SQLDialect dialect, DataType<T> 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<T> 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<T> type, String typeName) { this(dialect, null, type, typeName, typeName, 0, 0, 0, true, null); } public DefaultDataType(SQLDialect dialect, Class<T> type, String typeName, String castTypeName) { this(dialect, null, type, typeName, castTypeName, 0, 0, 0, true, null); } DefaultDataType(SQLDialect dialect, Class<T> type, String typeName, String castTypeName, int precision, int scale, int length, boolean nullable, Field<T> defaultValue) { this(dialect, null, type, typeName, castTypeName, precision, scale, length, nullable, defaultValue); } DefaultDataType(SQLDialect dialect, Class<T> type, Binding<?, T> binding, String typeName, String castTypeName, int precision, int scale, int length, boolean nullable, Field<T> defaultValue) { this(dialect, null, type, binding, typeName, castTypeName, precision, scale, length, nullable, defaultValue); } DefaultDataType(SQLDialect dialect, DataType<T> sqlDataType, Class<T> type, String typeName, String castTypeName, int precision, int scale, int length, boolean nullable, Field<T> defaultValue) { this(dialect, sqlDataType, type, null, typeName, castTypeName, precision, scale, length, nullable, defaultValue); } DefaultDataType(SQLDialect dialect, DataType<T> sqlDataType, Class<T> type, Binding<?, T> binding, String typeName, String castTypeName, int precision, int scale, int length, boolean nullable, Field<T> defaultValue) { this(dialect, sqlDataType, type, binding, typeName, castTypeName, precision, scale, length, nullable, false, defaultValue); } DefaultDataType(SQLDialect dialect, DataType<T> sqlDataType, Class<T> type, Binding<?, T> binding, String typeName, String castTypeName, int precision, int scale, int length, boolean nullable, boolean identity, Field<T> 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<T[]>) Array.newInstance(type, 0).getClass(); this.nullable = nullable; this.identity = identity; 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<T, T>(Converters.identity(type), this.isLob()); } /** * [#3225] Performant constructor for creating derived types. */ private DefaultDataType(DefaultDataType<T> t, int precision, int scale, int length, boolean nullable, boolean identity, Field<T> 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.identity = identity; 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<T> nullable(boolean n) { return new DefaultDataType<T>(this, precision, scale, length, n, n ? false : identity, defaultValue); } @Override public final boolean nullable() { return nullable; } @Override public final DataType<T> identity(boolean i) { return new DefaultDataType<T>(this, precision, scale, length, i ? false : nullable, i, i ? null : defaultValue); } @Override public final boolean identity() { return identity; } @Override public final DataType<T> defaultValue(T d) { return defaultValue(Tools.field(d, this)); } @Override public final DataType<T> defaultValue(Field<T> d) { return new DefaultDataType<T>(this, precision, scale, length, nullable, d != null ? false : identity, d); } @Override public final Field<T> defaultValue() { return defaultValue; } @Override @Deprecated public final DataType<T> defaulted(boolean d) { return defaultValue(d ? Tools.field(null, this) : null); } @Override public final boolean defaulted() { return defaultValue != null; } @Override public final DataType<T> precision(int p) { return precision(p, scale); } @Override public final DataType<T> 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<T>(this, p, s, length, nullable, identity, defaultValue); } @Override public final int precision() { return precision; } @Override public final boolean hasPrecision() { return type == BigInteger.class || type == BigDecimal.class; } @Override public final DataType<T> 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<T>(this, precision, s, length, nullable, identity, defaultValue); } @Override public final int scale() { return scale; } @Override public final boolean hasScale() { return type == BigDecimal.class; } @Override public final DataType<T> 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<T>(this, precision, scale, l, nullable, identity, 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<T> getSQLDataType() { return sqlDataType; } @Override public final DataType<T> 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<T>) 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() { return getSQLType(DSL.using(dialect).configuration()); } @Override public final int getSQLType(Configuration configuration) { // 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 (Tools.isDate(type)) { switch (configuration.family()) { default: 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 (Tools.isTime(type)) { return Types.TIME; } else if (Tools.isTimestamp(type)) { return Types.TIMESTAMP; } // [#5779] Few JDBC drivers support the JDBC 4.2 TIME[STAMP]_WITH_TIMEZONE types. else if (type == OffsetTime.class) { return Types.VARCHAR; } else if (type == OffsetDateTime.class) { return Types.VARCHAR; } // 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 (configuration.family()) { case H2: return -10; // OracleTypes.CURSOR; case POSTGRES: default: return Types.OTHER; } } else { return Types.OTHER; } } @Override public final Class<T> getType() { return type; } @Override public final Binding<?, T> getBinding() { return binding; } @Override public final Converter<?, T> getConverter() { return binding.converter(); } @Override public final Class<T[]> 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<T[]> getArrayDataType() { return new ArrayDataType<T>(this); } @Override public final <E extends EnumType> DataType<E> asEnumDataType(Class<E> enumDataType) { String enumTypeName = Tools.enums(enumDataType)[0].getName(); return new DefaultDataType<E>(dialect, enumDataType, enumTypeName, enumTypeName); } @Override public final <U> DataType<U> asConvertedDataType(Converter<? super T, U> converter) { return asConvertedDataType(DefaultBinding.newBinding(converter, this, null)); } @SuppressWarnings("deprecation") @Override public final <U> DataType<U> asConvertedDataType(Binding<? super T, U> newBinding) { if (binding == newBinding) return (DataType<U>) this; if (newBinding == null) newBinding = (Binding<? super T, U>) new DefaultBinding<T, T>(Converters.identity(getType()), isLob()); return new ConvertedDataType<T, U>(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<T> convert(Collection<?> objects) { return Convert.convert(objects, type); } public static DataType<Object> getDefaultDataType(String typeName) { return new DefaultDataType<Object>(SQLDialect.DEFAULT, Object.class, typeName, typeName); } public static DataType<Object> getDefaultDataType(SQLDialect dialect, String typeName) { return new DefaultDataType<Object>(dialect, Object.class, typeName, typeName); } public static DataType<?> getDataType(SQLDialect dialect, String typeName) { int ordinal = dialect.ordinal(); DataType<?> result = TYPES_BY_NAME[ordinal].get(typeName.toUpperCase()); // [#3225] Normalise only if necessary if (result == null) { typeName = DefaultDataType.normalise(typeName); result = TYPES_BY_NAME[ordinal].get(typeName); } // UDT data types and others are registered using DEFAULT if (result == null) result = TYPES_BY_NAME[SQLDialect.DEFAULT.ordinal()].get(typeName); // [#4065] PostgreSQL reports array types as _typename, e.g. _varchar if (result == null && dialect.family() == SQLDialect.POSTGRES && typeName.charAt(0) == '_') result = getDataType(dialect, typeName.substring(1)).getArrayDataType(); // [#366] Don't log a warning here. The warning is logged when // catching the exception in jOOQ-codegen if (result == null) throw new SQLDialectNotSupportedException("Type " + typeName + " is not supported in dialect " + dialect, false); return result; } public static <T> DataType<T> getDataType(SQLDialect dialect, Class<T> type) { return getDataType(dialect, type, null); } public static <T> DataType<T> getDataType(SQLDialect dialect, Class<T> type, DataType<T> fallbackDataType) { // Treat primitive types the same way as their respective wrapper types type = (Class<T>) wrapper(type); // Recurse for arrays if (byte[].class != type && type.isArray()) { return (DataType<T>) getDataType(dialect, type.getComponentType()).getArrayDataType(); } // Base types are registered statically else { DataType<?> result = null; if (dialect != null) { result = TYPES_BY_TYPE[dialect.family().ordinal()].get(type); } if (result == null) { // jOOQ data types are handled here try { if (UDTRecord.class.isAssignableFrom(type)) { return (DataType<T>) ((UDTRecord<?>) type.newInstance()).getUDT().getDataType(); } else if (EnumType.class.isAssignableFrom(type)) { return (DataType<T>) SQLDataType.VARCHAR.asEnumDataType((Class<EnumType>) type); } } catch (Exception e) { throw new MappingException("Cannot create instance of " + type, e); } } if (result == null) { if (SQL_DATATYPES_BY_TYPE.get(type) != null) { return (DataType<T>) SQL_DATATYPES_BY_TYPE.get(type); } // If we have a "fallback" data type from an outer context else if (fallbackDataType != null) { return fallbackDataType; } // All other data types are illegal else { throw new SQLDialectNotSupportedException("Type " + type + " is not supported in dialect " + dialect); } } return (DataType<T>) result; } } @Override public final boolean isNumeric() { return Number.class.isAssignableFrom(type) && !isInterval(); } @Override public final boolean isString() { return type == String.class; } @Override public final boolean isDateTime() { return java.util.Date.class.isAssignableFrom(type) || java.time.temporal.Temporal.class.isAssignableFrom(type) ; } @Override public final boolean isTemporal() { return isDateTime() || isInterval(); } @Override public final boolean isInterval() { return Interval.class.isAssignableFrom(type); } @Override public final boolean isLob() { DataType<T> t = getSQLDataType(); if (t == this) return getTypeName().endsWith("lob"); else return (t == BLOB || t == CLOB || t == NCLOB); } @Override public final boolean isBinary() { return type == byte[].class; } @Override public final boolean isArray() { return (!isBinary() && type.isArray()); } @Override public final boolean isUDT() { return UDTRecord.class.isAssignableFrom(type); } // ------------------------------------------------------------------------ // The Object API // ------------------------------------------------------------------------ @Override public String toString() { return getCastTypeName() + " (" + type.getName() + ")"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((dialect == null) ? 0 : dialect.hashCode()); result = prime * result + length; result = prime * result + precision; result = prime * result + scale; result = prime * result + ((type == null) ? 0 : type.hashCode()); result = prime * result + ((typeName == null) ? 0 : typeName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DefaultDataType<?> other = (DefaultDataType<?>) obj; if (dialect != other.dialect) return false; if (length != other.length) return false; if (precision != other.precision) return false; if (scale != other.scale) return false; if (type == null) { if (other.type != null) return false; } else if (!type.equals(other.type)) return false; if (typeName == null) { if (other.typeName != null) return false; } else if (!typeName.equals(other.typeName)) return false; return true; } /** * @return The type name without all special characters and white spaces */ public static String normalise(String typeName) { return NORMALISE_PATTERN.matcher(typeName.toUpperCase()).replaceAll(""); } /** * Convert a type name (using precision and scale) into a Java class */ public static DataType<?> getDataType(SQLDialect dialect, String t, int p, int s) throws SQLDialectNotSupportedException { DataType<?> result = DefaultDataType.getDataType(dialect, t); if (result.getType() == BigDecimal.class) { result = DefaultDataType.getDataType(dialect, getNumericClass(p, s)); } return result; } /** * Convert a type name (using precision and scale) into a Java class */ public static Class<?> getType(SQLDialect dialect, String t, int p, int s) throws SQLDialectNotSupportedException { return getDataType(dialect, t, p, s).getType(); } /** * Get the most suitable Java class for a given precision and scale */ private static Class<?> getNumericClass(int precision, int scale) { // Integer numbers if (scale == 0 && precision != 0) { if (precision < BYTE_PRECISION) { return Byte.class; } if (precision < SHORT_PRECISION) { return Short.class; } if (precision < INTEGER_PRECISION) { return Integer.class; } if (precision < LONG_PRECISION) { return Long.class; } // Default integer number return BigInteger.class; } // Real numbers should not be represented as float or double else { return BigDecimal.class; } } static Collection<Class<?>> types() { return unmodifiableCollection(SQL_DATATYPES_BY_TYPE.keySet()); } static Collection<DataType<?>> dataTypes() { return unmodifiableCollection(SQL_DATATYPES_BY_TYPE.values()); } }