/** * 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.sql.pg; import com.foundationdb.server.types.aksql.aktypes.AkGUID; import com.foundationdb.sql.server.ServerType; import com.foundationdb.sql.types.DataTypeDescriptor; import com.foundationdb.sql.types.TypeId; import com.foundationdb.ais.model.Column; import com.foundationdb.ais.model.Parameter; import com.foundationdb.server.error.UnknownDataTypeException; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.common.types.DecimalAttribute; import com.foundationdb.server.types.common.types.StringAttribute; import com.foundationdb.server.types.common.types.TBinary; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.Types; /** A type according to the PostgreSQL regime. * Information corresponds more-or-less directly to what's in the * <code>pg_attribute</code> table. * * The Type in ServerType is the type that is supposed to be there. e.g. if I have a prepared statement that expects an * MCOMPAT_ INT(11) NOT NULL, but then when binding on the client say that it will be a "text". * The resulting PostgresType will have a typeOid=TEXT_TYPE_OID and type=MCOMPAT_ INT(11) NOT NULL. */ public class PostgresType extends ServerType { private static Logger logger = LoggerFactory.getLogger(PostgresType.class); /*** Type OIDs ***/ public enum TypeOid { BOOL_TYPE_OID(16, "bool", BinaryEncoding.BOOLEAN_C, Types.BOOLEAN), BYTEA_TYPE_OID(17, "bytea", BinaryEncoding.BINARY_OCTAL_TEXT, Types.VARBINARY), CHAR_TYPE_OID(18, "char", BinaryEncoding.STRING_BYTES, Types.CHAR), NAME_TYPE_OID(19, "name"), INT8_TYPE_OID(20, "int8", BinaryEncoding.INT_64, Types.BIGINT), INT2_TYPE_OID(21, "int2", BinaryEncoding.INT_16, Types.SMALLINT), INT2VECTOR_TYPE_OID(22, "int2vector"), INT4_TYPE_OID(23, "int4", BinaryEncoding.INT_32, Types.INTEGER), REGPROC_TYPE_OID(24, "regproc"), TEXT_TYPE_OID(25, "text"), OID_TYPE_OID(26, "oid"), TID_TYPE_OID(27, "tid"), XID_TYPE_OID(28, "xid"), CID_TYPE_OID(29, "cid"), OIDVECTOR_TYPE_OID(30, "oidvector"), PG_TYPE_TYPE_OID(71, "pg_type", TypType.COMPOSITE), PG_ATTRIBUTE_TYPE_OID(75, "pg_attribute", TypType.COMPOSITE), PG_PROC_TYPE_OID(81, "pg_proc", TypType.COMPOSITE), PG_CLASS_TYPE_OID(83, "pg_class", TypType.COMPOSITE), JSON_TYPE_OID(114, "json", BinaryEncoding.STRING_BYTES, Types.VARCHAR), XML_TYPE_OID(142, "xml"), _XML_TYPE_OID(143, "_xml"), _JSON_TYPE_OID(199, "_json"), SMGR_TYPE_OID(210, "smgr"), POINT_TYPE_OID(600, "point"), LSEG_TYPE_OID(601, "lseg"), PATH_TYPE_OID(602, "path"), BOX_TYPE_OID(603, "box"), POLYGON_TYPE_OID(604, "polygon"), LINE_TYPE_OID(628, "line"), _LINE_TYPE_OID(629, "_line"), FLOAT4_TYPE_OID(700, "float4", BinaryEncoding.FLOAT_32, Types.REAL), FLOAT8_TYPE_OID(701, "float8", BinaryEncoding.FLOAT_64, Types.DOUBLE), ABSTIME_TYPE_OID(702, "abstime"), RELTIME_TYPE_OID(703, "reltime"), TINTERVAL_TYPE_OID(704, "tinterval"), UNKNOWN_TYPE_OID(705, "unknown"), CIRCLE_TYPE_OID(718, "circle"), _CIRCLE_TYPE_OID(719, "_circle"), MONEY_TYPE_OID(790, "money"), _MONEY_TYPE_OID(791, "_money"), MACADDR_TYPE_OID(829, "macaddr"), INET_TYPE_OID(869, "inet"), CIDR_TYPE_OID(650, "cidr"), _BOOL_TYPE_OID(1000, "_bool"), _BYTEA_TYPE_OID(1001, "_bytea"), _CHAR_TYPE_OID(1002, "_char"), _NAME_TYPE_OID(1003, "_name"), _INT2_TYPE_OID(1005, "_int2"), _INT2VECTOR_TYPE_OID(1006, "_int2vector"), _INT4_TYPE_OID(1007, "_int4"), _REGPROC_TYPE_OID(1008, "_regproc"), _TEXT_TYPE_OID(1009, "_text"), _OID_TYPE_OID(1028, "_oid"), _TID_TYPE_OID(1010, "_tid"), _XID_TYPE_OID(1011, "_xid"), _CID_TYPE_OID(1012, "_cid"), _OIDVECTOR_TYPE_OID(1013, "_oidvector"), _BPCHAR_TYPE_OID(1014, "_bpchar"), _VARCHAR_TYPE_OID(1015, "_varchar"), _INT8_TYPE_OID(1016, "_int8"), _POINT_TYPE_OID(1017, "_point"), _LSEG_TYPE_OID(1018, "_lseg"), _PATH_TYPE_OID(1019, "_path"), _BOX_TYPE_OID(1020, "_box"), _FLOAT4_TYPE_OID(1021, "_float4"), _FLOAT8_TYPE_OID(1022, "_float8"), _ABSTIME_TYPE_OID(1023, "_abstime"), _RELTIME_TYPE_OID(1024, "_reltime"), _TINTERVAL_TYPE_OID(1025, "_tinterval"), _POLYGON_TYPE_OID(1027, "_polygon"), ACLITEM_TYPE_OID(1033, "_aclitem"), _ACLITEM_TYPE_OID(1034, "_aclitem"), _MACADDR_TYPE_OID(1040, "_macaddr"), _INET_TYPE_OID(1041, "_inet"), _CIDR_TYPE_OID(651, "_cidr"), _CSTRING_TYPE_OID(1263, "_cstring"), BPCHAR_TYPE_OID(1042, "bpchar", BinaryEncoding.STRING_BYTES, Types.VARCHAR), VARCHAR_TYPE_OID(1043, "varchar", BinaryEncoding.STRING_BYTES, Types.VARCHAR), DATE_TYPE_OID(1082, "date", BinaryEncoding.DAYS_2000, Types.DATE), TIME_TYPE_OID(1083, "time", BinaryEncoding.TIME_INT64_MICROS_NOTZ, Types.TIME), TIMESTAMP_TYPE_OID(1114, "timestamp", BinaryEncoding.TIMESTAMP_INT64_MICROS_2000_NOTZ, Types.TIMESTAMP), _TIMESTAMP_TYPE_OID(1115, "_timestamp"), _DATE_TYPE_OID(1182, "_date"), _TIME_TYPE_OID(1183, "_time"), TIMESTAMPTZ_TYPE_OID(1184, "timestamptz"), _TIMESTAMPTZ_TYPE_OID(1185, "_timestamptz"), INTERVAL_TYPE_OID(1186, "interval"), _INTERVAL_TYPE_OID(1187, "_interval"), _NUMERIC_TYPE_OID(1231, "_numeric"), TIMETZ_TYPE_OID(1266, "timetz"), _TIMETZ_TYPE_OID(1270, "_timetz"), BIT_TYPE_OID(1560, "bit"), _BIT_TYPE_OID(1561, "_bit"), VARBIT_TYPE_OID(1562, "varbit"), _VARBIT_TYPE_OID(1563, "_varbit"), NUMERIC_TYPE_OID(1700, "numeric", BinaryEncoding.DECIMAL_PG_NUMERIC_VAR, Types.DECIMAL), REFCURSOR_TYPE_OID(1790, "refcursor"), _REFCURSOR_TYPE_OID(2201, "_refcursor"), REGPROCEDURE_TYPE_OID(2202, "regprocedure"), REGOPER_TYPE_OID(2203, "regoper"), REGOPERATOR_TYPE_OID(2204, "regoperator"), UUID_TYPE_OID(2950, "uuid", BinaryEncoding.UUID, Types.OTHER), _UUID_TYPE_OID(2951, "_uuid", BinaryEncoding.UUID, Types.OTHER); enum TypType { BASE, COMPOSITE, DOMAIN, ENUM, PSEUDO; } private int oid; private String name; private TypType type; private BinaryEncoding binaryEncoding; private int jdbcType; TypeOid(int oid, String name, TypType type, BinaryEncoding binaryEncoding, int jdbcType) { this.oid = oid; this.name = name; this.type = type; this.binaryEncoding = binaryEncoding; this.jdbcType = jdbcType; } TypeOid(int oid, String name, TypType type) { this(oid, name, type, BinaryEncoding.NONE, Types.OTHER); } TypeOid(int oid, String name, BinaryEncoding binaryEncoding, int jdbcType) { this(oid, name, TypType.BASE, binaryEncoding, jdbcType); } TypeOid(int oid, String name) { this(oid, name, TypType.BASE, BinaryEncoding.NONE, Types.OTHER); } public int getOid() { return oid; } public String getName() { return name; } public TypType getType() { return type; } public BinaryEncoding getBinaryEncoding() { return binaryEncoding; } public int getJDBCType() { return jdbcType; } public static TypeOid fromOid(int oid) { for (TypeOid inst : values()) { if (inst.getOid() == oid) { return inst; } } return null; } } /*** Representation. ***/ private TypeOid oid; private short length; private int modifier; public PostgresType(TypeOid oid, short length, int modifier, TInstance type) { super(type); this.oid = oid; this.length = length; this.modifier = modifier; } @Override public BinaryEncoding getBinaryEncoding() { return oid.getBinaryEncoding(); } public int getOid() { return oid.getOid(); } public String getTypeName() { return oid.getName(); } public short getLength() { return length; } public int getModifier() { return modifier; } public static PostgresType fromAIS(Column aisColumn) { return fromTInstance(aisColumn.getType()); } public static PostgresType fromAIS(Parameter aisParameter) { return fromTInstance(aisParameter.getType()); } public static PostgresType fromTInstance(TInstance type) { TClass tClass = TInstance.tClass(type); TypeOid oid; switch (tClass.jdbcType()) { case Types.CHAR: case Types.NCHAR: /* TODO: Should be: oid = TypeOid.CHAR_TYPE_OID; break; */ case Types.NVARCHAR: case Types.VARCHAR: oid = TypeOid.VARCHAR_TYPE_OID; break; case Types.BOOLEAN: oid = TypeOid.BOOL_TYPE_OID; break; case Types.TINYINT: oid = TypeOid.INT2_TYPE_OID; // No INT1 break; case Types.SMALLINT: if (tClass.isUnsigned()) oid = TypeOid.INT4_TYPE_OID; else oid = TypeOid.INT2_TYPE_OID; break; case Types.INTEGER: if (tClass.isUnsigned()) oid = TypeOid.INT8_TYPE_OID; else oid = TypeOid.INT4_TYPE_OID; break; case Types.BIGINT: if (tClass.isUnsigned()) // Closest exact numeric type capable of holding 64-bit unsigned is DEC(20). return new PostgresType(TypeOid.NUMERIC_TYPE_OID, (short)8, (20 << 16) + 4, type); else oid = TypeOid.INT8_TYPE_OID; break; case Types.DECIMAL: case Types.NUMERIC: oid = TypeOid.NUMERIC_TYPE_OID; break; case Types.FLOAT: case Types.REAL: oid = TypeOid.FLOAT4_TYPE_OID; break; case Types.DOUBLE: oid = TypeOid.FLOAT8_TYPE_OID; break; case Types.DATE: oid = TypeOid.DATE_TYPE_OID; break; case Types.TIME: oid = TypeOid.TIME_TYPE_OID; break; case Types.TIMESTAMP: oid = TypeOid.TIMESTAMP_TYPE_OID; break; case Types.BINARY: case Types.BIT: case Types.LONGVARBINARY: case Types.VARBINARY: case Types.BLOB: oid = TypeOid.BYTEA_TYPE_OID; break; case Types.LONGNVARCHAR: case Types.LONGVARCHAR: case Types.CLOB: oid = TypeOid.TEXT_TYPE_OID; break; case Types.OTHER: if (tClass == AkGUID.INSTANCE){ oid = TypeOid.UUID_TYPE_OID; break; } default: // Tell Postgres layer to just parse / format a string. oid = TypeOid.VARCHAR_TYPE_OID; break; } short length = -1; int modifier = -1; if (tClass.hasFixedSerializationSize()) length = (short)tClass.fixedSerializationSize(); if (type.hasAttributes(StringAttribute.class)) { // VARCHAR(n). modifier = type.attribute(StringAttribute.MAX_LENGTH) + 4; } else if (type.hasAttributes(TBinary.Attrs.class)) { // VARBINARY(n). modifier = type.attribute(TBinary.Attrs.LENGTH) + 4; } else if (type.hasAttributes(DecimalAttribute.class)) { // NUMERIC(n,m). modifier = (type.attribute(DecimalAttribute.PRECISION) << 16) + type.attribute(DecimalAttribute.SCALE) + 4; } return new PostgresType(oid, length, modifier, type); } public static PostgresType fromDerby(DataTypeDescriptor sqlType, final TInstance type) { TypeOid oid; short length = -1; int modifier = -1; TypeId typeId = sqlType.getTypeId(); switch (typeId.getTypeFormatId()) { case TypeId.FormatIds.INTERVAL_DAY_SECOND_ID: oid = TypeOid.INTERVAL_TYPE_OID; break; case TypeId.FormatIds.INTERVAL_YEAR_MONTH_ID: oid = TypeOid.INTERVAL_TYPE_OID; break; case TypeId.FormatIds.BIT_TYPE_ID: oid = TypeOid.BYTEA_TYPE_OID; break; case TypeId.FormatIds.BOOLEAN_TYPE_ID: oid = TypeOid.BOOL_TYPE_OID; break; case TypeId.FormatIds.CHAR_TYPE_ID: oid = TypeOid.BPCHAR_TYPE_OID; break; case TypeId.FormatIds.DATE_TYPE_ID: oid = TypeOid.DATE_TYPE_OID; break; case TypeId.FormatIds.DECIMAL_TYPE_ID: case TypeId.FormatIds.NUMERIC_TYPE_ID: oid = TypeOid.NUMERIC_TYPE_OID; break; case TypeId.FormatIds.DOUBLE_TYPE_ID: oid = TypeOid.FLOAT8_TYPE_OID; break; case TypeId.FormatIds.INT_TYPE_ID: if (typeId.isUnsigned()) oid = TypeOid.INT8_TYPE_OID; else oid = TypeOid.INT4_TYPE_OID; break; case TypeId.FormatIds.LONGINT_TYPE_ID: if (typeId.isUnsigned()) { return new PostgresType(TypeOid.NUMERIC_TYPE_OID, (short)8, (20 << 16) + 4, type); } oid = TypeOid.INT8_TYPE_OID; break; case TypeId.FormatIds.LONGVARBIT_TYPE_ID: oid = TypeOid.TEXT_TYPE_OID; break; case TypeId.FormatIds.LONGVARCHAR_TYPE_ID: oid = TypeOid.TEXT_TYPE_OID; break; case TypeId.FormatIds.REAL_TYPE_ID: oid = TypeOid.FLOAT4_TYPE_OID; break; case TypeId.FormatIds.SMALLINT_TYPE_ID: if (typeId.isUnsigned()) oid = TypeOid.INT4_TYPE_OID; else oid = TypeOid.INT2_TYPE_OID; break; case TypeId.FormatIds.TIME_TYPE_ID: oid = TypeOid.TIME_TYPE_OID; break; case TypeId.FormatIds.TIMESTAMP_TYPE_ID: // TODO: MDatetimes.TIMESTAMP is MYSQL_TIMESTAMP, another // way of representing seconds precision, not ISO // timestamp with fractional seconds. oid = TypeOid.TIMESTAMP_TYPE_OID; break; case TypeId.FormatIds.TINYINT_TYPE_ID: oid = TypeOid.INT2_TYPE_OID; // No INT1, room for unsigned break; case TypeId.FormatIds.VARBIT_TYPE_ID: oid = TypeOid.BYTEA_TYPE_OID; break; case TypeId.FormatIds.BLOB_TYPE_ID: // may need update oid = TypeOid.BYTEA_TYPE_OID; break; case TypeId.FormatIds.VARCHAR_TYPE_ID: oid = TypeOid.VARCHAR_TYPE_OID; break; case TypeId.FormatIds.CLOB_TYPE_ID: oid = TypeOid.TEXT_TYPE_OID; break; case TypeId.FormatIds.XML_TYPE_ID: oid = TypeOid.XML_TYPE_OID; break; case TypeId.FormatIds.GUID_TYPE_ID: oid = TypeOid.UUID_TYPE_OID; break; case TypeId.FormatIds.USERDEFINED_TYPE_ID: default: throw new UnknownDataTypeException(sqlType.toString()); } if (typeId.isDecimalTypeId() || typeId.isNumericTypeId()) { modifier = (sqlType.getPrecision() << 16) + sqlType.getScale() + 4; } else if (typeId.variableLength()) { modifier = sqlType.getMaximumWidth() + 4; } else { length = (short)typeId.getMaximumMaximumWidth(); } return new PostgresType(oid, length, modifier, type); } public static int toJDBC(int oid) { TypeOid typeOid = TypeOid.fromOid(oid); if (typeOid == null) return Types.OTHER; else return typeOid.getJDBCType(); } @Override public String toString() { StringBuilder str = new StringBuilder(super.toString()); if (length >= 0) { str.append("(").append(length); if (modifier >= 0) str.append(",").append(modifier); str.append(")"); } str.append("/").append(oid.getName()); return str.toString(); } }