/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.types.common.types;
import com.foundationdb.server.error.UnknownDataTypeException;
import com.foundationdb.server.error.UnsupportedColumnDataTypeException;
import com.foundationdb.server.error.UnsupportedDataTypeException;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.aksql.aktypes.AkBlob;
import com.foundationdb.server.types.aksql.aktypes.AkBool;
import com.foundationdb.server.types.aksql.aktypes.AkGUID;
import com.foundationdb.server.types.aksql.aktypes.AkInterval;
import com.foundationdb.server.types.aksql.aktypes.AkResultSet;
import com.foundationdb.server.types.common.BigDecimalWrapper;
import com.foundationdb.server.types.common.types.StringAttribute;
import com.foundationdb.server.types.common.types.StringFactory;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.types.value.ValueTarget;
import com.foundationdb.sql.types.CharacterTypeAttributes;
import com.foundationdb.sql.types.DataTypeDescriptor;
import com.foundationdb.sql.types.TypeId;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Objects;
import java.util.List;
/**
* Translate between types in particular bundle(s) and Java / standard
* SQL types. SQL types are represented via Derby (sql-parser) types
* or <code>java.sql.Types</code> members.
*/
public abstract class TypesTranslator
{
/** Get a <code>TClass</code> for the default encoding of strings. */
public TClass typeClassForString() {
return typeClassForJDBCType(Types.VARCHAR);
}
/** Get a <code>TInstance</code> for the an arbitrary length string. */
public TInstance typeForString() {
return typeClassForString().instance(Integer.MAX_VALUE, false);
}
/** Get a <code>TInstance</code> for the given string. */
public TInstance typeForString(String value) {
if (value == null)
return typeClassForString().instance(1, true);
else
return typeClassForString().instance(value.codePointCount(0, value.length()), false);
}
public TInstance typeForString(int length, String charset, String collation,
int defaultCharsetId, int defaultCollationId,
boolean nullable) {
TClass tclass = typeClassForString();
assert tclass.hasAttributes(StringAttribute.class) : tclass;
int charsetId = defaultCharsetId, collationId = defaultCollationId;
if (charset != null) {
charsetId = StringFactory.charsetNameToId(charset);
}
if (collation != null) {
collationId = StringFactory.collationNameToId(collation);
}
return tclass.instance(length, charsetId, collationId, nullable);
}
public TClass typeClassForBinary() {
return typeClassForJDBCType(Types.VARBINARY);
}
public TClass typeClassForSystemTimestamp() {
return typeClassForJDBCType(Types.TIMESTAMP);
}
public int jdbcType(TInstance type) {
TClass tclass = TInstance.tClass(type);
if (tclass == null)
return Types.OTHER;
else
return tclass.jdbcType();
}
public Class<?> jdbcClass(TInstance type) {
TClass tclass = TInstance.tClass(type);
if (tclass == null)
return Object.class;
int jdbcType = tclass.jdbcType();
switch (jdbcType) {
case Types.DECIMAL:
case Types.NUMERIC:
return BigDecimal.class;
case Types.BOOLEAN:
return Boolean.class;
case Types.TINYINT:
return Byte.class;
case Types.BINARY:
case Types.BIT:
case Types.LONGVARBINARY:
case Types.VARBINARY:
case Types.BLOB:
return byte[].class;
case Types.DATE:
return java.sql.Date.class;
case Types.DOUBLE:
return Double.class;
case Types.FLOAT:
case Types.REAL:
return Float.class;
case Types.INTEGER:
return Integer.class;
case Types.BIGINT:
return Long.class;
case Types.SMALLINT:
return Short.class;
case Types.CHAR:
case Types.LONGNVARCHAR:
case Types.LONGVARCHAR:
case Types.NCHAR:
case Types.NVARCHAR:
case Types.VARCHAR:
case Types.CLOB:
return String.class;
case Types.TIME:
return java.sql.Time.class;
case Types.TIMESTAMP:
return java.sql.Timestamp.class;
/*
case Types.ARRAY:
return java.sql.Array.class;
case Types.BLOB:
return java.sql.Blob.class;
case Types.CLOB:
return java.sql.Clob.class;
case Types.NCLOB:
return java.sql.NClob.class;
case Types.REF:
return java.sql.Ref.class;
case Types.ROWID:
return java.sql.RowId.class;
case Types.SQLXML:
return java.sql.SQLXML.class;
*/
case Types.NULL:
case Types.DATALINK:
case Types.DISTINCT:
case Types.JAVA_OBJECT:
case Types.OTHER:
case Types.STRUCT:
default:
break;
}
if (tclass == AkResultSet.INSTANCE)
return java.sql.ResultSet.class;
return Object.class;
}
/** Does this type represent a signed numeric type? */
public boolean isTypeSigned(TInstance type) {
TClass tclass = TInstance.tClass(type);
if (tclass == null)
return false;
switch (tclass.jdbcType()) {
case Types.BIGINT:
case Types.DECIMAL:
case Types.DOUBLE:
case Types.FLOAT:
case Types.INTEGER:
case Types.NUMERIC:
case Types.REAL:
case Types.SMALLINT:
case Types.TINYINT:
return true;
default:
return false;
}
}
public boolean isTypeUnsigned(TInstance type) {
TClass tclass = TInstance.tClass(type);
if (tclass == null)
return false;
else
return tclass.isUnsigned();
}
/** Give a <code>ValueSource</code> whose {@link #jdbcType} claims
* to be one of the integer types (<code>TINYINT</code>,
* <code>SMALLINT</code>, <code>INTEGER</code>,
* <code>BIGINT<code>), get the integer value. Needed because of
* <code>UNSIGNED</code> and <code>YEAR</code> types.
*/
public long getIntegerValue(ValueSource value) {
switch (TInstance.underlyingType(value.getType())) {
case INT_8:
return value.getInt8();
case INT_16:
return value.getInt16();
case UINT_16:
return value.getUInt16();
case INT_32:
return value.getInt32();
case INT_64:
default:
return value.getInt64();
}
}
/**
* @see #getIntegerValue
*/
public void setIntegerValue(ValueTarget target, long value) {
switch (TInstance.underlyingType(target.getType())) {
case INT_8:
target.putInt8((byte)value);
break;
case INT_16:
target.putInt16((short)value);
break;
case UINT_16:
target.putUInt16((char)value);
break;
case INT_32:
target.putInt32((int)value);
break;
case INT_64:
default:
target.putInt64(value);
}
}
/** Give a <code>ValueSource</code> whose {@link #jdbcType} claims
* to be one of the decimal types (<code>DECIMAL</code>,
* <code>NUMERIC</code>), get the decimal value.
*/
public BigDecimal getDecimalValue(ValueSource value) {
return TBigDecimal.getWrapper(value, value.getType()).asBigDecimal();
}
/** Give a <code>ValueSource</code> whose {@link #jdbcType} claims
* to be one of the date/time types (<code>DATE</code>,
* <code>TIME</code>, <code>TIMESTAMP</code>), get the
* milliseconds portion. In general, the seconds portion of this
* value should be zero. Needed because of <code>DATETIME</code>.
* @see #getTimestampNanosValue
*/
public abstract long getTimestampMillisValue(ValueSource value);
/** Give a <code>ValueSource</code> whose {@link #jdbcType} claims
* to be <code>TIMESTAMP</code>, get the nanoseconds portion.
* @see #getTimestampNanosValue
*/
public int getTimestampNanosValue(ValueSource value) {
return 0;
}
/**
* @see #getTimestampMillisValue
*/
public abstract void setTimestampMillisValue(ValueTarget value, long millis, int nanos);
/**
* Translate the given parser type to the corresponding type instance.
* Will only return null if sqlType is null
*/
public TInstance typeForSQLType(DataTypeDescriptor sqlType) {
return typeForSQLType(sqlType,
StringFactory.DEFAULT_CHARSET_ID,
StringFactory.DEFAULT_COLLATION_ID);
}
/** Will only return null if sqlType is null **/
public TInstance typeForSQLType(DataTypeDescriptor sqlType,
String schemaName, String tableName, String columnName) {
return typeForSQLType(sqlType,
StringFactory.DEFAULT_CHARSET_ID,
StringFactory.DEFAULT_COLLATION_ID,
schemaName, tableName, columnName);
}
/** Will only return null if sqlType is null **/
public TInstance typeForSQLType(DataTypeDescriptor sqlType,
int defaultCharsetId, int defaultCollationId) {
return typeForSQLType(sqlType, defaultCharsetId, defaultCollationId,
null, null, null);
}
/** Will only return null if sqlType is null **/
public TInstance typeForSQLType(DataTypeDescriptor sqlType,
int defaultCharsetId, int defaultCollationId,
String schemaName, String tableName, String columnName) {
TInstance type;
if (sqlType == null)
return null;
else
return typeForSQLType(sqlType.getTypeId(), sqlType,
defaultCharsetId, defaultCollationId,
schemaName, tableName, columnName);
}
/**
*
* @return not null
* @throws com.foundationdb.server.error.UnsupportedDataTypeException
* @throws com.foundationdb.server.error.UnsupportedColumnDataTypeException
*/
protected TInstance typeForSQLType(TypeId typeId, DataTypeDescriptor sqlType,
int defaultCharsetId, int defaultCollationId,
String schemaName, String tableName, String columnName) {
switch (typeId.getTypeFormatId()) {
/* No attribute types. */
case TypeId.FormatIds.TINYINT_TYPE_ID:
return typeForJDBCType(Types.TINYINT, sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.SMALLINT_TYPE_ID:
return typeForJDBCType(Types.SMALLINT, sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.MEDIUMINT_ID:
case TypeId.FormatIds.INT_TYPE_ID:
return typeForJDBCType(Types.INTEGER, sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.LONGINT_TYPE_ID:
return typeForJDBCType(Types.BIGINT, sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.DATE_TYPE_ID:
return typeForJDBCType(Types.DATE, sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.TIME_TYPE_ID:
return typeForJDBCType(Types.TIME, sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.TIMESTAMP_TYPE_ID:
return typeForJDBCType(Types.TIMESTAMP, sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.REAL_TYPE_ID:
return typeForJDBCType(Types.REAL, sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.DOUBLE_TYPE_ID:
return typeForJDBCType(Types.DOUBLE, sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.BLOB_TYPE_ID:
return typeForJDBCType(Types.BLOB, sqlType.isNullable(),
schemaName, tableName, columnName);
/* Width attribute types. */
case TypeId.FormatIds.BIT_TYPE_ID:
return typeForJDBCType(Types.BIT, sqlType.getMaximumWidth(), sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.VARBIT_TYPE_ID:
return typeForJDBCType(Types.VARBINARY, sqlType.getMaximumWidth(), sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.LONGVARBIT_TYPE_ID:
return typeForJDBCType(Types.LONGVARBINARY, sqlType.isNullable(),
schemaName, tableName, columnName);
/* Precision, scale attribute types. */
case TypeId.FormatIds.DECIMAL_TYPE_ID:
return typeForJDBCType(Types.DECIMAL, sqlType.getPrecision(), sqlType.getScale(), sqlType.isNullable(),
schemaName, tableName, columnName);
case TypeId.FormatIds.NUMERIC_TYPE_ID:
return typeForJDBCType(Types.NUMERIC, sqlType.getPrecision(), sqlType.getScale(), sqlType.isNullable(),
schemaName, tableName, columnName);
/* String (charset, collation) attribute types. */
case TypeId.FormatIds.CHAR_TYPE_ID:
return typeForStringType(Types.CHAR, sqlType,
defaultCharsetId, defaultCollationId,
schemaName, tableName, columnName);
case TypeId.FormatIds.VARCHAR_TYPE_ID:
return typeForStringType(Types.VARCHAR, sqlType,
defaultCharsetId, defaultCollationId,
schemaName, tableName, columnName);
case TypeId.FormatIds.LONGVARCHAR_TYPE_ID:
return typeForStringType(Types.LONGVARCHAR, sqlType,
defaultCharsetId, defaultCollationId,
schemaName, tableName, columnName);
case TypeId.FormatIds.CLOB_TYPE_ID:
return typeForStringType(Types.LONGVARCHAR, sqlType, // TODO: Types.CLOB
defaultCharsetId, defaultCollationId,
schemaName, tableName, columnName);
case TypeId.FormatIds.XML_TYPE_ID:
return typeForStringType(Types.SQLXML, sqlType,
defaultCharsetId, defaultCollationId,
schemaName, tableName, columnName);
/* Special case AkSQL types. */
case TypeId.FormatIds.BOOLEAN_TYPE_ID:
return AkBool.INSTANCE.instance(sqlType.isNullable());
case TypeId.FormatIds.INTERVAL_DAY_SECOND_ID:
return AkInterval.SECONDS.typeFrom(sqlType);
case TypeId.FormatIds.INTERVAL_YEAR_MONTH_ID:
return AkInterval.MONTHS.typeFrom(sqlType);
case TypeId.FormatIds.ROW_MULTISET_TYPE_ID_IMPL:
{
TypeId.RowMultiSetTypeId rmsTypeId =
(TypeId.RowMultiSetTypeId)typeId;
String[] columnNames = rmsTypeId.getColumnNames();
DataTypeDescriptor[] columnTypes = rmsTypeId.getColumnTypes();
List<AkResultSet.Column> columns = new ArrayList<>(columnNames.length);
for (int i = 0; i < columnNames.length; i++) {
columns.add(new AkResultSet.Column(columnNames[i],
typeForSQLType(columnTypes[i])));
}
return AkResultSet.INSTANCE.instance(columns);
}
case TypeId.FormatIds.GUID_TYPE_ID:
return AkGUID.INSTANCE.instance(sqlType.isNullable());
case TypeId.FormatIds.USERDEFINED_TYPE_ID:
{
String name = typeId.getSQLTypeName();
TClass tclass = typeClassForUserDefined(name);
return tclass.instance(sqlType.isNullable());
}
default:
if (columnName != null) {
throw new UnsupportedColumnDataTypeException(schemaName, tableName, columnName,
sqlType.toString());
}
else {
throw new UnsupportedDataTypeException(sqlType.toString());
}
}
}
protected TClass typeClassForUserDefined(String name) {
throw new UnknownDataTypeException(name);
}
/** never returns null **/
public TInstance typeForJDBCType(int jdbcType, boolean nullable,
String schemaName, String tableName, String columnName) {
TClass tclass = typeClassForJDBCType(jdbcType, schemaName, tableName, columnName);
return tclass.instance(nullable);
}
/** never returns null **/
public TInstance typeForJDBCType(int jdbcType, int att, boolean nullable,
String schemaName, String tableName, String columnName) {
TClass tclass = typeClassForJDBCType(jdbcType, schemaName, tableName, columnName);
return tclass.instance(att, nullable);
}
/** never returns null **/
public TInstance typeForJDBCType(int jdbcType, int att1, int att2, boolean nullable,
String schemaName, String tableName, String columnName) {
TClass tclass = typeClassForJDBCType(jdbcType, schemaName, tableName, columnName);
return tclass.instance(att1, att2, nullable);
}
/** never returns null **/
protected TInstance typeForStringType(int jdbcType, DataTypeDescriptor type,
int defaultCharsetId, int defaultCollationId,
String schemaName, String tableName, String columnName) {
TClass tclass = typeClassForJDBCType(jdbcType, schemaName, tableName, columnName);
return typeForStringType(tclass, type,
defaultCharsetId, defaultCollationId,
schemaName, tableName, columnName);
}
/** never returns null **/
protected TInstance typeForStringType(TClass tclass, DataTypeDescriptor type,
int defaultCharsetId, int defaultCollationId,
String schemaName, String tableName, String columnName) {
int charsetId, collationId;
CharacterTypeAttributes typeAttributes = type.getCharacterAttributes();
if ((typeAttributes == null) || (typeAttributes.getCharacterSet() == null)) {
charsetId = defaultCharsetId;
}
else {
charsetId = StringFactory.charsetNameToId(typeAttributes.getCharacterSet());
}
if ((typeAttributes == null) || (typeAttributes.getCollation() == null)) {
collationId = defaultCollationId;
}
else {
collationId = StringFactory.collationNameToId(typeAttributes.getCollation());
}
return tclass.instance(type.getMaximumWidth(),
charsetId, collationId,
type.isNullable());
}
public TClass typeClassForJDBCType(int jdbcType) {
return typeClassForJDBCType(jdbcType, null, null, null);
}
/**
* This does not return null
* @throws com.foundationdb.server.error.UnsupportedColumnDataTypeException if an appropriate TClass can't be found
* @throws com.foundationdb.server.error.UnsupportedDataTypeException if an appropriate TClass can't be found
*/
public TClass typeClassForJDBCType(int jdbcType,
String schemaName, String tableName, String columnName) {
switch (jdbcType) {
case Types.BLOB:
return AkBlob.INSTANCE;
case Types.BOOLEAN:
return AkBool.INSTANCE;
case Types.ARRAY:
case Types.DATALINK:
case Types.DISTINCT:
case Types.JAVA_OBJECT:
case Types.NULL:
case Types.OTHER:
case Types.REF:
case Types.ROWID:
case Types.STRUCT:
default:
if (columnName != null) {
throw new UnsupportedColumnDataTypeException(schemaName, tableName, columnName,
jdbcTypeName(jdbcType));
}
else {
throw new UnsupportedDataTypeException(jdbcTypeName(jdbcType));
}
}
}
protected static String jdbcTypeName(int jdbcType) {
try {
for (Field field : Types.class.getFields()) {
if (((field.getModifiers() & Modifier.STATIC) != 0) &&
Objects.equals(jdbcType, field.get(null))) {
return field.getName();
}
}
}
catch (Exception ex) {
}
return String.format("JDBC #%s", jdbcType);
}
}