package com.tesora.dve.db.mysql; /* * #%L * Tesora Inc. * Database Virtualization Engine * %% * Copyright (C) 2011 - 2014 Tesora Inc. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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/>. * #L% */ import java.sql.DatabaseMetaData; import java.sql.Types; import com.tesora.dve.db.NativeType; import com.tesora.dve.db.NativeTypeAlias; import com.tesora.dve.db.mysql.common.ColumnAttributes; import com.tesora.dve.exceptions.PECodingException; import com.tesora.dve.exceptions.PEException; public class MysqlNativeType extends NativeType { public static final String MODIFIER_UNSIGNED = "unsigned"; public static final String MODIFIER_SIGNED = "signed"; public static final String MODIFIER_ZEROFILL = "zerofill"; private static final long serialVersionUID = 1L; private final MysqlType mysqlType; private byte charSet; private short fieldTypeFlags; // used by PEMysqlProtocolConverter /** * Enum representing all of the MySQL native types. The toMysqlType method can be used to convert a string * representing a MySQL type into this enumeration * */ public enum MysqlType { NULL(MyFieldType.FIELD_TYPE_NULL, Types.NULL, false), // LONG_NVARCHAR(MyFieldType.FIELD_TYPE_BLOB, Types.LONGNVARCHAR, false), // NCHAR(MyFieldType.FIELD_TYPE_STRING, Types.NCHAR, false), // NVARCHAR(MyFieldType.FIELD_TYPE_VAR_STRING, Types.NVARCHAR, false), BIT(MyFieldType.FIELD_TYPE_BIT, Types.BIT), BOOL(MyFieldType.FIELD_TYPE_TINY, Types.BIT, new NativeTypeAlias[] { new NativeTypeAlias("BOOLEAN") }), TINYINT(MyFieldType.FIELD_TYPE_TINY, Types.TINYINT, new NativeTypeAlias[] { new NativeTypeAlias("INT1"), new NativeTypeAlias("TINYINT UNSIGNED", true) }), BIGINT(MyFieldType.FIELD_TYPE_LONGLONG, Types.BIGINT, new NativeTypeAlias[] { new NativeTypeAlias("INT8"), new NativeTypeAlias("BIGINT UNSIGNED", true) }), //LONG_VARBINARY(MyFieldType.FIELD_TYPE_BLOB, Types.LONGVARBINARY), MEDIUMBLOB(MyFieldType.FIELD_TYPE_BLOB, Types.LONGVARBINARY, new NativeTypeAlias[] { new NativeTypeAlias("LONG_VARBINARY") }), LONGBLOB(MyFieldType.FIELD_TYPE_BLOB, Types.LONGVARBINARY), // DAS - these were added because the defintion of LONG/MEDIUM/TINY BLOB seems to change if it is in // a column defintion vs a transient column (compare col1 longblob vs select @sql_mode) ALTLONGBLOB(MyFieldType.FIELD_TYPE_LONG_BLOB, Types.LONGVARBINARY), ALTMEDIUMBLOB(MyFieldType.FIELD_TYPE_MEDIUM_BLOB, Types.LONGVARBINARY), ALTTINYBLOB(MyFieldType.FIELD_TYPE_TINY_BLOB, Types.LONGVARBINARY), // BLOB(MyFieldType.FIELD_TYPE_BLOB, Types.LONGVARBINARY), TINYBLOB(MyFieldType.FIELD_TYPE_BLOB, Types.LONGVARBINARY), VARBINARY(MyFieldType.FIELD_TYPE_VAR_STRING, Types.VARBINARY), BINARY(MyFieldType.FIELD_TYPE_STRING, Types.BINARY), //LONG_VARCHAR(MyFieldType.FIELD_TYPE_BLOB, Types.LONGVARCHAR), MEDIUMTEXT(MyFieldType.FIELD_TYPE_BLOB, Types.LONGVARCHAR, new NativeTypeAlias[] { new NativeTypeAlias("LONG_VARCHAR") }), LONGTEXT(MyFieldType.FIELD_TYPE_BLOB, Types.LONGVARCHAR), TEXT(MyFieldType.FIELD_TYPE_BLOB, Types.LONGVARCHAR), TINYTEXT(MyFieldType.FIELD_TYPE_BLOB, Types.LONGVARCHAR), CHAR(MyFieldType.FIELD_TYPE_STRING, Types.CHAR, new NativeTypeAlias[] { new NativeTypeAlias("CHARACTER"), new NativeTypeAlias("NCHAR") }), // NUMERIC(MyFieldType.FIELD_TYPE_NEWDECIMAL, Types.NUMERIC), DECIMAL(MyFieldType.FIELD_TYPE_NEWDECIMAL, Types.DECIMAL, new NativeTypeAlias[] { new NativeTypeAlias("DEC"), new NativeTypeAlias("FIXED"), new NativeTypeAlias("NUMERIC") }), // INT(MyFieldType.FIELD_TYPE_LONG, Types.INTEGER, new NativeTypeAlias[] { new NativeTypeAlias("INT UNSIGNED", true) }), // INTEGER(MyFieldType.FIELD_TYPE_LONG, Types.INTEGER, new NativeTypeAlias[] { new NativeTypeAlias("INT4"), // new NativeTypeAlias("INTEGER UNSIGNED", true) }), INT(MyFieldType.FIELD_TYPE_LONG, Types.INTEGER, new NativeTypeAlias[] { new NativeTypeAlias("INT UNSIGNED", true), new NativeTypeAlias("INT4"), new NativeTypeAlias("INTEGER", true), new NativeTypeAlias("INTEGER UNSIGNED", true) }), MEDIUMINT(MyFieldType.FIELD_TYPE_INT24, Types.INTEGER, new NativeTypeAlias[] { new NativeTypeAlias("MEDIUMINT UNSIGNED", true), new NativeTypeAlias("INT3"), new NativeTypeAlias("MIDDLEINT") }), SMALLINT(MyFieldType.FIELD_TYPE_SHORT, Types.SMALLINT, new NativeTypeAlias[] { new NativeTypeAlias("INT2"), new NativeTypeAlias("SMALLINT UNSIGNED", true) }), FLOAT(MyFieldType.FIELD_TYPE_FLOAT, Types.REAL), DOUBLE(MyFieldType.FIELD_TYPE_DOUBLE, Types.DOUBLE, new NativeTypeAlias[] { new NativeTypeAlias("FLOAT8") }), DOUBLE_PRECISION(MyFieldType.FIELD_TYPE_DOUBLE, Types.DOUBLE, new NativeTypeAlias[] { new NativeTypeAlias("REAL", true) }), VARCHAR(MyFieldType.FIELD_TYPE_VAR_STRING, Types.VARCHAR, new NativeTypeAlias[] { new NativeTypeAlias("CHARACTER VARYING"), new NativeTypeAlias("VARCHARACTER"), new NativeTypeAlias("NVARCHAR") }), DATE(MyFieldType.FIELD_TYPE_DATE, Types.DATE), TIME(MyFieldType.FIELD_TYPE_TIME, Types.TIME), TIMESTAMP(MyFieldType.FIELD_TYPE_TIMESTAMP, Types.TIMESTAMP), DATETIME(MyFieldType.FIELD_TYPE_DATETIME, Types.TIMESTAMP), YEAR(MyFieldType.FIELD_TYPE_YEAR, Types.DATE, false), PARAMETER(MyFieldType.FIELD_TYPE_VAR_STRING, Types.VARCHAR, false), ENUM(MyFieldType.FIELD_TYPE_ENUM, Types.VARCHAR), SET(MyFieldType.FIELD_TYPE_SET, Types.VARCHAR), // REAL(Types.DOUBLE), // this is now an alias of DOUBLE // REAL_UNUSED(MyFieldType.FIELD_TYPE_FLOAT, Types.FLOAT, false), /* Spatial types. */ GEOMETRY(MyFieldType.FIELD_TYPE_GEOMETRY, Types.BINARY), POINT(MyFieldType.FIELD_TYPE_GEOMETRY, Types.BINARY), LINESTRING(MyFieldType.FIELD_TYPE_GEOMETRY, Types.BINARY), POLYGON(MyFieldType.FIELD_TYPE_GEOMETRY, Types.BINARY), GEOMETRYCOLLECTION(MyFieldType.FIELD_TYPE_GEOMETRY, Types.BINARY), MULTIPOINT(MyFieldType.FIELD_TYPE_GEOMETRY, Types.BINARY), MULTILINESTRING(MyFieldType.FIELD_TYPE_GEOMETRY, Types.BINARY), MULTIPOLYGON(MyFieldType.FIELD_TYPE_GEOMETRY, Types.BINARY), UNKNOWN(MyFieldType.FIELD_TYPE_NULL, Types.NULL, false); private MysqlType(MyFieldType mft, int sqlType) { this(mft, sqlType, true, null); } private MysqlType(MyFieldType mft, int sqlType, boolean jdbcType) { this(mft, sqlType, jdbcType, null); } private MysqlType(MyFieldType mft, int sqlType, NativeTypeAlias[] aliases) { this(mft, sqlType, true, aliases); } private MysqlType(MyFieldType mft, int sqlType, boolean jdbcType, NativeTypeAlias[] aliases) { this.name = NativeType.fixName(super.toString(), true); this.mft = mft; this.sqlType = sqlType; this.jdbcType = jdbcType; this.aliases = aliases; this.isValid = this.toString().equalsIgnoreCase("UNKNOWN") ? false : true; } private final String name; private final MyFieldType mft; private final int sqlType; private final boolean jdbcType; private final NativeTypeAlias[] aliases; private final boolean isValid; private static MysqlNativeTypeCatalog mntCatalog = null; public static MysqlType toMysqlType(String name) { String fixedName = NativeType.fixNameForType(name); try { return valueOf(fixedName); } catch (IllegalArgumentException iae) { // maybe it's an alias try { if (mntCatalog == null) { mntCatalog = new MysqlNativeTypeCatalog(); mntCatalog.load(); } return valueOf(NativeType.fixNameForType( ((MysqlNativeType) mntCatalog.findType(fixedName, true)).getMysqlType().toString())); } catch (IllegalArgumentException iae2) { return UNKNOWN; } catch (PEException e) { return UNKNOWN; } } } public static MysqlType toMysqlType(int nativeTypeId) { for (MysqlType v : values()) { if (v.sqlType == nativeTypeId) return v; } return null; } public boolean isValid() { return isValid; } public int getSqlType() { return sqlType; } public MyFieldType getMysqlFieldType() { return mft; } public NativeTypeAlias[] getAliases() { return aliases; } public boolean isJdbcType() { return jdbcType; } public static MysqlNativeTypeCatalog getMntCatalog() { return mntCatalog; } @Override public String toString() { return name; } } // @SuppressWarnings("unused") // private MysqlNativeType() { // // don't allow constructor without a type // this.mysqlType = null; // } /** * Main constructor, based on the MysqlType enum * * @param mysqlType */ public MysqlNativeType(MysqlType mysqlType) { super(mysqlType.toString(), mysqlType.getMysqlFieldType().getByteValue(), mysqlType.getSqlType(), mysqlType.isJdbcType(), mysqlType.getAliases()); this.mysqlType = mysqlType; // all mysql types are nullable setNullability((short) DatabaseMetaData.typeNullable); setProperCaseSensitive(); setProperUnsignedAttribute(); if (mysqlType == MysqlType.UNKNOWN) { throw new PECodingException("Type name " + mysqlType + " is not a valid MySQL type"); } } public MysqlType getMysqlType() { return mysqlType; } public byte getCharSet() { return charSet; } public void setCharSet(byte charSet) { this.charSet = charSet; } @SuppressWarnings("hiding") public MysqlNativeType withCharSet(byte charSet) { setCharSet(charSet); return this; } public short getFieldTypeFlags() { return fieldTypeFlags; } public void setFieldTypeFlags(short fieldTypeFlags) { this.fieldTypeFlags = fieldTypeFlags; // case sensitivity and unsigned are based on these flags, so (re)set them here setProperCaseSensitive(); setProperUnsignedAttribute(); } @SuppressWarnings("hiding") public MysqlNativeType withFieldTypeFlags(short fieldTypeFlags) { setFieldTypeFlags(fieldTypeFlags); return this; } @SuppressWarnings("hiding") public MysqlNativeType withFieldTypeFlags(Integer fieldTypeFlags) { setFieldTypeFlags(fieldTypeFlags.shortValue()); return this; } /** * Case sensitivity is hardcoded in mysql and cannot be set * * @return */ @Override public void setCaseSensitive(boolean caseSensitive) { throw new RuntimeException("Cannot set 'case sensitive' attribute in mysql (on type " + getTypeName() + ")"); } /** * In the MySQL DatabaseMetaData class there is a getTypeInfo() method that sets the case sensitive bit. * However, that bit is not used by the official MySQL driver. It turns out case sensitive value is hardcoded, * so we set it whenever a type is created * * @return */ private void setProperCaseSensitive() { boolean caseSensitive = false; int sqlType = getDataType(); switch (sqlType) { // NOPMD by doug on 18/12/12 7:36 AM case Types.BIT: case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: case Types.FLOAT: case Types.REAL: case Types.DOUBLE: case Types.DATE: case Types.TIME: case Types.TIMESTAMP: caseSensitive = false; break; case Types.NUMERIC: case Types.DECIMAL: caseSensitive = true; break; case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: if (isBinary()) { caseSensitive = true; } // TODO: Need to augment this to match official MySQL driver // Currently we know if the collation sequence is LATIN1_SWEDISH_CI // (default) // then we should return false. if (getCharSet() == MysqlNativeConstants.MYSQL_CHARSET_UTF8) { caseSensitive = false; } // String collationName = field.getCollation(); // // return ((collationName != null) && // !collationName.endsWith("_ci")); break; default: caseSensitive = true; } super.setCaseSensitive(caseSensitive); } /** * Unsigned attribute is hardcoded in mysql and cannot be set * * @return */ @Override public void setUnsignedAttribute(boolean unsignedAttribute) { throw new RuntimeException("Cannot set 'unsigned' attribute in mysql"); } /** * In the MySQL DatabaseMetaData class there is a getTypeInfo() method that sets the unsigned attribute bit. * However, that bit is not used by the official MySQL driver. It turns out unsigned attribute value is hardcoded, * so we set it whenever a type is created * * @return */ private void setProperUnsignedAttribute() { boolean unsigned = false; int sqlType = getDataType(); switch (sqlType) { // NOPMD by doug on 18/12/12 7:36 AM case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: case Types.DOUBLE: case Types.REAL: case Types.FLOAT: case Types.NUMERIC: case Types.DECIMAL: unsigned = !isUnsigned(); break; case Types.DATE: case Types.TIME: case Types.TIMESTAMP: unsigned = false; break; default: unsigned = false; } super.setUnsignedAttribute(unsigned); } @Override public boolean asKeyRequiresPrefix() { switch(mysqlType) { case MEDIUMBLOB: case LONGBLOB: case BLOB: case TINYBLOB: case MEDIUMTEXT: case LONGTEXT: case TEXT: case TINYTEXT: return true; default: return false; } } @Override public Object getZeroValue() throws PEException { switch(mysqlType) { case BIGINT: case BIT: case BOOL: case INT: // case INTEGER: // case NUMERIC: case SMALLINT: case MEDIUMINT: case TINYINT: case DOUBLE: case DECIMAL: case DOUBLE_PRECISION: case FLOAT: return new Integer(0); case CHAR: // case LONG_NVARCHAR: // case LONG_VARCHAR: case TEXT: case LONGTEXT: case MEDIUMTEXT: // case NCHAR: // case NVARCHAR: case TINYTEXT: case VARCHAR: case BLOB: case LONGBLOB: case ALTLONGBLOB: // case LONG_VARBINARY: case MEDIUMBLOB: case ALTMEDIUMBLOB: case TINYBLOB: case ALTTINYBLOB: case VARBINARY: case BINARY: return ""; case DATE: return "0000-00-00"; case TIME: return "00:00:00"; case DATETIME: case TIMESTAMP: return "0000-00-00 00:00:00"; case YEAR: return new Integer(0); default: throw new PEException("No zero value known for type " + mysqlType); } } @Override public boolean supportsDefaultValue() { switch(mysqlType) { case BLOB: case LONGBLOB: case MEDIUMBLOB: case TINYBLOB: case TEXT: case LONGTEXT: case MEDIUMTEXT: case TINYTEXT: return false; default: return true; } } @Override public boolean isTimestampType() { return mysqlType == MysqlType.TIMESTAMP; } private boolean isBinary() { return ((getFieldTypeFlags() & MysqlNativeConstants.FLDPKT_FLAG_BINARY) > 0); } private boolean isUnsigned() { return ((getFieldTypeFlags() & MysqlNativeConstants.FLDPKT_FLAG_UNSIGNED) > 0); } @Override public String toString() { return "MysqlNativeType [mysqlType=" + mysqlType + ", charSet=" + charSet + "]"; } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((mysqlType == null) ? 0 : mysqlType.hashCode()); result = prime * result + charSet; result = prime * result + fieldTypeFlags; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; MysqlNativeType other = (MysqlNativeType) obj; if (mysqlType != other.mysqlType) return false; if (charSet != other.charSet) return false; if (fieldTypeFlags != other.fieldTypeFlags) return false; return true; } @Override public int getDefaultColumnAttrFlags() { switch(mysqlType) { case BIT: case BIGINT: case BINARY: case BLOB: case CHAR: case INT: case MEDIUMINT: case LONGBLOB: case LONGTEXT: case MEDIUMBLOB: case MEDIUMTEXT: case SMALLINT: case TEXT: case TINYBLOB: case TINYINT: case TINYTEXT: case VARBINARY: case VARCHAR: case YEAR: return ColumnAttributes.SIZED_TYPE; case DECIMAL: case DOUBLE_PRECISION: case DOUBLE: case FLOAT: return ColumnAttributes.PS_TYPE; default: return 0; } } }