package liquibase.database.typeconversion.core; import liquibase.change.ColumnConfig; import liquibase.database.Database; import liquibase.database.core.*; import liquibase.database.structure.Column; import liquibase.database.structure.type.*; import liquibase.database.typeconversion.TypeConverter; import liquibase.exception.DateParseException; import liquibase.exception.UnexpectedLiquibaseException; import liquibase.statement.DatabaseFunction; import liquibase.util.StringUtils; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Types; import java.text.ParseException; import java.util.Arrays; import java.util.List; import java.util.logging.Logger; public abstract class AbstractTypeConverter implements TypeConverter { final Logger logger = Logger.getLogger(getClass().getName()); public Object convertDatabaseValueToObject(Object value, int databaseDataType, int firstParameter, int secondParameter, Database database) throws ParseException { if (value == null) { return null; } else if (value instanceof String) { return convertToCorrectObjectType(((String) value).replaceFirst("^'", "").replaceFirst("'$", ""), databaseDataType, firstParameter, secondParameter, database); } else { return value; } } public DataType getDataType(Object object) { if (object instanceof BigInteger) { return getBigIntType(); } else if (object instanceof Boolean) { return getBooleanType(); } else if (object instanceof String) { return getVarcharType(); } else if (object instanceof java.sql.Date) { return getDateType(); } else if (object instanceof java.sql.Timestamp) { return getDateTimeType(); } else if (object instanceof java.sql.Time) { return getTimeType(); } else if (object instanceof java.util.Date) { return getDateTimeType(); } else if (object instanceof Double) { return getDoubleType(); } else if (object instanceof Float) { return getFloatType(); } else if (object instanceof Integer) { return getIntType(); } else if (object instanceof Long) { return getBigIntType(); } else if (object instanceof DatabaseFunction) { return new DatabaseFunctionType(); } else { throw new UnexpectedLiquibaseException("Unknown object type "+object.getClass().getName()); } } protected Object convertToCorrectObjectType(String value, int dataType, int columnSize, int decimalDigits, Database database) throws ParseException { if (value == null) { return null; } if (dataType == Types.CLOB || dataType == Types.VARCHAR || dataType == Types.CHAR || dataType == Types.LONGVARCHAR) { if (value.equalsIgnoreCase("NULL")) { return null; } else { return value; } } value = StringUtils.trimToNull(value); if (value == null) { return null; } try { if (dataType == Types.DATE) { return new java.sql.Date(database.parseDate(value).getTime()); } else if (dataType == Types.TIMESTAMP) { return new java.sql.Timestamp(database.parseDate(value).getTime()); } else if (dataType == Types.TIME) { return new java.sql.Time(database.parseDate(value).getTime()); } else if (dataType == Types.BIGINT) { return new BigInteger(value); } else if (dataType == Types.BIT) { value = value.replaceFirst("b'", ""); //mysql puts wierd chars in bit field if (value.equalsIgnoreCase("true")) { return Boolean.TRUE; } else if (value.equalsIgnoreCase("false")) { return Boolean.FALSE; } else if (value.equals("1")) { return Boolean.TRUE; } else if (value.equals("0")) { return Boolean.FALSE; } else if (value.equals("(1)")) { return Boolean.TRUE; } else if (value.equals("(0)")) { return Boolean.FALSE; } throw new ParseException("Unknown bit value: " + value, 0); } else if (dataType == Types.BOOLEAN) { return Boolean.valueOf(value); } else if (dataType == Types.DECIMAL) { if (decimalDigits == 0) { return new Integer(value); } return new BigDecimal(value); } else if (dataType == Types.DOUBLE || dataType == Types.NUMERIC) { return new BigDecimal(value); } else if (dataType == Types.FLOAT) { return new Float(value); } else if (dataType == Types.INTEGER) { return new Integer(value); } else if (dataType == Types.NULL) { return null; } else if (dataType == Types.REAL) { return new Float(value); } else if (dataType == Types.SMALLINT) { return new Integer(value); } else if (dataType == Types.TINYINT) { return new Integer(value); } else if (dataType == Types.BLOB) { return "!!!!!! LIQUIBASE CANNOT OUTPUT BLOB VALUES !!!!!!"; } else { logger.warning("Do not know how to convert type " + dataType); return value; } } catch (DateParseException e) { return new DatabaseFunction(value); } catch (NumberFormatException e) { return new DatabaseFunction(value); } } /** * Returns the database-specific datatype for the given column configuration. * This method will convert some generic column types (e.g. boolean, currency) to the correct type * for the current database. */ public DataType getDataType(String columnTypeString, Boolean autoIncrement) { // Parse out data type and precision // Example cases: "CLOB", "java.sql.Types.CLOB", "CLOB(10000)", "java.sql.Types.CLOB(10000) String dataTypeName = null; String precision = null; String additionalInformation = null; if (columnTypeString.startsWith("java.sql.Types") && columnTypeString.contains("(")) { precision = columnTypeString.substring(columnTypeString.indexOf("(") + 1, columnTypeString.indexOf(")")); dataTypeName = columnTypeString.substring(columnTypeString.lastIndexOf(".") + 1, columnTypeString.indexOf("(")); } else if (columnTypeString.startsWith("java.sql.Types")) { dataTypeName = columnTypeString.substring(columnTypeString.lastIndexOf(".") + 1); } else if (columnTypeString.contains("(")) { precision = columnTypeString.substring(columnTypeString.indexOf("(") + 1, columnTypeString.indexOf(")")); dataTypeName = columnTypeString.substring(0, columnTypeString.indexOf("(")); } else { dataTypeName = columnTypeString; } if (columnTypeString.contains(")")) { additionalInformation = StringUtils.trimToNull(columnTypeString.replaceFirst(".*\\)", "")); } return getDataType(columnTypeString, autoIncrement, dataTypeName, precision, additionalInformation); } protected DataType getDataType(String columnTypeString, Boolean autoIncrement, String dataTypeName, String precision, String additionalInformation) { // Translate type to database-specific type, if possible DataType returnTypeName = null; if (dataTypeName.equalsIgnoreCase("BIGINT")) { returnTypeName = getBigIntType(); } else if (dataTypeName.equalsIgnoreCase("NUMBER") || dataTypeName.equalsIgnoreCase("NUMERIC")) { returnTypeName = getNumberType(); } else if (dataTypeName.equalsIgnoreCase("BLOB")) { returnTypeName = getBlobType(); } else if (dataTypeName.equalsIgnoreCase("BOOLEAN")) { returnTypeName = getBooleanType(); } else if (dataTypeName.equalsIgnoreCase("CHAR")) { returnTypeName = getCharType(); } else if (dataTypeName.equalsIgnoreCase("CLOB")) { returnTypeName = getClobType(); } else if (dataTypeName.equalsIgnoreCase("CURRENCY")) { returnTypeName = getCurrencyType(); } else if (dataTypeName.equalsIgnoreCase("DATE") || dataTypeName.equalsIgnoreCase(getDateType().getDataTypeName())) { returnTypeName = getDateType(); } else if (dataTypeName.equalsIgnoreCase("DATETIME") || dataTypeName.equalsIgnoreCase(getDateTimeType().getDataTypeName())) { returnTypeName = getDateTimeType(); } else if (dataTypeName.equalsIgnoreCase("DOUBLE")) { returnTypeName = getDoubleType(); } else if (dataTypeName.equalsIgnoreCase("FLOAT")) { returnTypeName = getFloatType(); } else if (dataTypeName.equalsIgnoreCase("INT")) { returnTypeName = getIntType(); } else if (dataTypeName.equalsIgnoreCase("INTEGER")) { returnTypeName = getIntType(); } else if (dataTypeName.equalsIgnoreCase("LONGBLOB")) { returnTypeName = getLongBlobType(); } else if (dataTypeName.equalsIgnoreCase("LONGVARBINARY")) { returnTypeName = getBlobType(); } else if (dataTypeName.equalsIgnoreCase("LONGVARCHAR")) { returnTypeName = getClobType(); } else if (dataTypeName.equalsIgnoreCase("SMALLINT")) { returnTypeName = getSmallIntType(); } else if (dataTypeName.equalsIgnoreCase("TEXT")) { returnTypeName = getClobType(); } else if (dataTypeName.equalsIgnoreCase("TIME") || dataTypeName.equalsIgnoreCase(getTimeType().getDataTypeName())) { returnTypeName = getTimeType(); } else if (dataTypeName.toUpperCase().contains("TIMESTAMP")) { returnTypeName = getDateTimeType(); } else if (dataTypeName.equalsIgnoreCase("TINYINT")) { returnTypeName = getTinyIntType(); } else if (dataTypeName.equalsIgnoreCase("UUID")) { returnTypeName = getUUIDType(); } else if (dataTypeName.equalsIgnoreCase("VARCHAR")) { returnTypeName = getVarcharType(); } else if (dataTypeName.equalsIgnoreCase("NVARCHAR")) { returnTypeName = getNVarcharType(); } else { return new CustomType(columnTypeString,0,2); } if (returnTypeName == null) { throw new UnexpectedLiquibaseException("Could not determine " + dataTypeName + " for " + this.getClass().getName()); } addPrecisionToType(precision, returnTypeName); returnTypeName.setAdditionalInformation(additionalInformation); return returnTypeName; } protected void addPrecisionToType(String precision, DataType returnTypeName) throws NumberFormatException { if (precision != null) { String[] params = precision.split(","); returnTypeName.setFirstParameter(params[0].trim()); if (params.length > 1) { returnTypeName.setSecondParameter(params[1].trim()); } } } public DataType getDataType(ColumnConfig columnConfig) { return getDataType(columnConfig.getType(), columnConfig.isAutoIncrement()); } /** * Returns the actual database-specific data type to use a "date" (no time information) column. */ public DateType getDateType() { return new DateType(); } /** * Returns the actual database-specific data type to use a "time" column. */ public TimeType getTimeType() { return new TimeType(); } public DateTimeType getDateTimeType() { return new DateTimeType(); } public BigIntType getBigIntType() { return new BigIntType(); } /** * Returns the actual database-specific data type to use for a "char" column. */ public CharType getCharType() { return new CharType(); } /** * Returns the actual database-specific data type to use for a "varchar" column. */ public VarcharType getVarcharType() { return new VarcharType(); } /** * Returns the actual database-specific data type to use for a "varchar" column. */ public NVarcharType getNVarcharType() { return new NVarcharType(); } /** * Returns the actual database-specific data type to use for a "float" column. * * @return database-specific type for float */ public FloatType getFloatType() { return new FloatType(); } /** * Returns the actual database-specific data type to use for a "double" column. * * @return database-specific type for double */ public DoubleType getDoubleType() { return new DoubleType(); } /** * Returns the actual database-specific data type to use for a "int" column. * * @return database-specific type for int */ public IntType getIntType() { return new IntType(); } /** * Returns the actual database-specific data type to use for a "tinyint" column. * * @return database-specific type for tinyint */ public TinyIntType getTinyIntType() { return new TinyIntType(); } public SmallIntType getSmallIntType() { return new SmallIntType(); } public BooleanType getBooleanType() { return new BooleanType(); } public NumberType getNumberType() { return new NumberType(); } public CurrencyType getCurrencyType() { return new CurrencyType(); } public UUIDType getUUIDType() { return new UUIDType(); } public TextType getTextType() { return getClobType(); } public ClobType getClobType() { return new ClobType(); } public BlobType getBlobType() { return new BlobType(); } public BlobType getLongBlobType() { return getBlobType(); } public String convertToDatabaseTypeString(Column referenceColumn, Database database) { List<Integer> noParens = Arrays.asList( Types.ARRAY, Types.BIGINT, Types.BINARY, Types.BIT, Types.BLOB, Types.BOOLEAN, Types.CLOB, Types.DATALINK, Types.DATE, Types.DISTINCT, Types.INTEGER, Types.JAVA_OBJECT, Types.LONGVARBINARY, Types.NULL, Types.OTHER, Types.REF, Types.SMALLINT, Types.STRUCT, Types.TIME, Types.TIMESTAMP, Types.TINYINT, Types.LONGVARCHAR); List<Integer> oneParam = Arrays.asList( Types.CHAR, -15, // Types.NCHAR in java 1.6, Types.VARCHAR, -9, //Types.NVARCHAR in java 1.6, Types.VARBINARY, Types.DOUBLE, Types.FLOAT ); List<Integer> twoParams = Arrays.asList( Types.DECIMAL, Types.NUMERIC, Types.REAL ); String translatedTypeName = referenceColumn.getTypeName(); if (database instanceof PostgresDatabase) { if ("bpchar".equals(translatedTypeName)) { translatedTypeName = "char"; } } if (database instanceof HsqlDatabase || database instanceof H2Database || database instanceof DerbyDatabase || database instanceof DB2Database) { if (referenceColumn.getDataType() == Types.FLOAT || referenceColumn.getDataType() == Types.DOUBLE) { return "float"; } } if (database instanceof InformixDatabase) { /* * rs.getInt("DATA_TYPE") returns 1 (Types.CHAR) for * interval types (bug in JDBC driver?) * So if you comment this out, the the columnsize will be appended * and the type becomes: "INTERVAL HOUR TO FRACTION(3)(2413)" */ if (translatedTypeName.toUpperCase().startsWith("INTERVAL")) { return translatedTypeName; } if (referenceColumn.getDataType() == Types.REAL) { return "SMALLFLOAT"; } } String dataType; if (noParens.contains(referenceColumn.getDataType())) { dataType = translatedTypeName; } else if (oneParam.contains(referenceColumn.getDataType())) { if (database instanceof PostgresDatabase && translatedTypeName.equalsIgnoreCase("TEXT")) { return translatedTypeName; } else if (database instanceof MSSQLDatabase && translatedTypeName.equals("uniqueidentifier")) { return translatedTypeName; } else if (database instanceof MySQLDatabase && (translatedTypeName.startsWith("enum(") || translatedTypeName.startsWith("set(")) ) { return translatedTypeName; } else if (database instanceof OracleDatabase && (translatedTypeName.equals("VARCHAR2")) ) { return translatedTypeName+"("+referenceColumn.getColumnSize()+" "+referenceColumn.getLengthSemantics()+")"; } else if (database instanceof MySQLDatabase && translatedTypeName.equalsIgnoreCase("DOUBLE")) { return translatedTypeName; } else if (database instanceof MySQLDatabase && translatedTypeName.equalsIgnoreCase("DOUBLE PRECISION")) { return translatedTypeName; } dataType = translatedTypeName+"("+referenceColumn.getColumnSize()+")"; } else if (twoParams.contains(referenceColumn.getDataType())) { if (database instanceof PostgresDatabase && referenceColumn.getColumnSize() == 131089 ) { dataType = "DECIMAL"; } else if (database instanceof MSSQLDatabase && translatedTypeName.toLowerCase().contains("money")) { dataType = translatedTypeName.toUpperCase(); } else { dataType = translatedTypeName; if (referenceColumn.isInitPrecision()) { dataType += "(" + referenceColumn.getColumnSize() + "," + referenceColumn.getDecimalDigits() + ")"; } } } else { logger.warning("Unknown Data Type: "+referenceColumn.getDataType()+" ("+referenceColumn.getTypeName()+"). Assuming it does not take parameters"); dataType = referenceColumn.getTypeName(); } return dataType; } }