liquibase-core/src/main/java/liquibase/datatype/DataTypeFactory.java
public LiquibaseDataType fromDescription(String dataTypeDefinition, Database database) {
        if (dataTypeDefinition == null) {
            return null;
        }
        String dataTypeName = dataTypeDefinition;

        // Remove the first occurrence of (anything within parentheses). This will remove the size information from
        // most data types, e.g. VARCHAR2(255 CHAR) -> VARCHAR2. We will retrieve that length information again later,
        // but for the moment, we are only interested in the "naked" data type name.
        if (dataTypeName.matches(".+\\(.*\\).*")) {
            dataTypeName = dataTypeName.replaceFirst("\\s*\\(.*\\)", "");
        }

        // Remove everything { after the first opening curly bracket
        // e.g. int{autoIncrement:true}" -> "int"
        if (dataTypeName.matches(".+\\{.*")) {
            dataTypeName = dataTypeName.replaceFirst("\\s*\\{.*", "");
        }

        // If the remaining string ends with " identity", then remove the " identity" and remember than we want
        // to set the autoIncrement property later.
        boolean autoIncrement = false;
        if (dataTypeName.toLowerCase(Locale.US).endsWith(" identity")) {
            dataTypeName = dataTypeName.toLowerCase(Locale.US).replaceFirst(" identity$", "");
            autoIncrement = true;
        }

        // unquote delimited identifiers
        final String[][] quotePairs = new String[][] {
            { "\"", "\"" }, // double quotes
            { "[",  "]"  }, // square brackets (a la mssql)
            { "`",  "`"  }, // backticks (a la mysql)
            { "'",  "'"  }  // single quotes
        };

        for (String[] quotePair : quotePairs) {
            String openQuote = quotePair[0];
            String closeQuote = quotePair[1];
            if (dataTypeName.startsWith(openQuote)) {
                int indexOfCloseQuote = dataTypeName.indexOf(closeQuote, openQuote.length());
                if ((indexOfCloseQuote != -1) && (dataTypeName.indexOf(closeQuote, indexOfCloseQuote + closeQuote
                    .length()) == -1)) {
                    dataTypeName = dataTypeName.substring(openQuote.length(), indexOfCloseQuote) +
                            dataTypeName.substring(indexOfCloseQuote + closeQuote.length(), dataTypeName.length());
                    break;
                }
            }
        }

        // record additional information that is still attached to the data type name
        String additionalInfo = null;
        if (dataTypeName.toLowerCase(Locale.US).startsWith("bit varying")
            || dataTypeName.toLowerCase(Locale.US).startsWith("character varying")) {
            // not going to do anything. Special case for postgres in our tests,
            // need to better support handling these types of differences
        } else {
            // Heuristic: from what we now have left of the data type name, everything after the first space
            // is counted as additional information.
            String[] splitTypeName = dataTypeName.trim().split("\\s+", 2);
            dataTypeName = splitTypeName[0];
            if (splitTypeName.length > 1) {
                additionalInfo = splitTypeName[1];
            }
        }

        // try to find matching classes for the data type name in our registry
        Collection<Class<? extends LiquibaseDataType>> classes = registry.get(dataTypeName.toLowerCase(Locale.US));

        LiquibaseDataType liquibaseDataType = null;
        if (classes == null) {
            // Map (date/time) INTERVAL types to the UnknownType
            if (dataTypeName.toUpperCase(Locale.US).startsWith("INTERVAL")) {
                liquibaseDataType = new UnknownType(dataTypeDefinition);
            } else {
                liquibaseDataType = new UnknownType(dataTypeName);
            }
        } else {
            // Iterate through the list (which is already sorted by priority) until we find a class
            // for this dataTypeName that supports the given database.
            Iterator<Class<? extends LiquibaseDataType>> iterator = classes.iterator();
            do {
                try {
                    liquibaseDataType = iterator.next().getConstructor().newInstance();
                } catch (Exception e) {
                    throw new UnexpectedLiquibaseException(e);
                }
            } while ((database != null) && !liquibaseDataType.supports(database) && iterator.hasNext());
        }
        if ((database != null) && !liquibaseDataType.supports(database)) {
            throw new UnexpectedLiquibaseException("Could not find type for " + liquibaseDataType.toString() +
                    " for DBMS "+database.getShortName());
        }
        if (liquibaseDataType == null) {
            liquibaseDataType = new UnknownType(dataTypeName);
        }
        liquibaseDataType.setAdditionalInformation(additionalInfo);

        // Does the type string have the form "some_data_type(additional,info,separated,by,commas)"?
        // If so, process these as additional data type parameters.
        if (dataTypeDefinition.matches(".+\\s*\\(.*")) {
            // Cut out the part between the first ()
            String paramStrings = dataTypeDefinition.replaceFirst(".*?\\(", "").replaceFirst("\\).*", "");
            String[] params = paramStrings.split(",");

            for (String param : params) {
                param = StringUtil.trimToNull(param);
                if (param != null) {
                    if ((liquibaseDataType instanceof CharType) && !(database instanceof OracleDatabase)) {
                        // TODO this might lead to wrong snapshot results in Oracle Database, because it assumes
                        // NLS_LENGTH_SEMANTICS=BYTE. If NLS_LENGTH_SEMANTICS=CHAR, we need to trim " CHAR" instead.
    
                        // not sure what else supports it:
                        param = param.replaceFirst(" BYTE", ""); //only use byte types on oracle,
                        
                    }
                    liquibaseDataType.addParameter(param);
                }
            }
        }

        // Did the original definition have embedded information in curly braces, e.g.
        // "int{autoIncrement:true}"? If so, we will extract and process it now.
        if (dataTypeDefinition.matches(".*\\{.*")) {
            String paramStrings = dataTypeDefinition.replaceFirst(".*?\\{", "")
                .replaceFirst("\\}.*", "");
            String[] params = paramStrings.split(",");
            for (String param : params) {
                param = StringUtil.trimToNull(param);
                if (param != null) {
                    String[] paramAndValue = param.split(":", 2);
                    // TODO: A run-time exception will occur here if the user writes a property name into the
                    // data type which does not exist - but what else could we do in this case, except aborting?
                    ObjectUtil.setProperty(liquibaseDataType, paramAndValue[0], paramAndValue[1]);
                }
            }
        }

        if (autoIncrement && (liquibaseDataType instanceof IntType)) {
            ((IntType) liquibaseDataType).setAutoIncrement(true);
        }
        if (autoIncrement && (liquibaseDataType instanceof BigIntType)) {
            ((BigIntType) liquibaseDataType).setAutoIncrement(true);
        }

        liquibaseDataType.finishInitialization(dataTypeDefinition);

        return liquibaseDataType;

    }
public LiquibaseDataType fromDescription(String dataTypeDefinition, Database database) {
        if (dataTypeDefinition == null) {
            return null;
        }
        String dataTypeName = dataTypeDefinition;

        // Remove the first occurrence of (anything within parentheses). This will remove the size information from
        // most data types, e.g. VARCHAR2(255 CHAR) -> VARCHAR2. We will retrieve that length information again later,
        // but for the moment, we are only interested in the "naked" data type name.
        if (dataTypeName.matches(".+\\(.*\\).*")) {
            dataTypeName = dataTypeName.replaceFirst("\\s*\\(.*\\)", "");
        }

        // Remove everything { after the first opening curly bracket
        // e.g. int{autoIncrement:true}" -> "int"
        if (dataTypeName.matches(".+\\{.*")) {
            dataTypeName = dataTypeName.replaceFirst("\\s*\\{.*", "");
        }

        // If the remaining string ends with " identity", then remove the " identity" and remember than we want
        // to set the autoIncrement property later.
        boolean autoIncrement = false;
        if (dataTypeName.toLowerCase(Locale.US).endsWith(" identity")) {
            dataTypeName = dataTypeName.toLowerCase(Locale.US).replaceFirst(" identity$", "");
            autoIncrement = true;
        }

        // unquote delimited identifiers
        final String[][] quotePairs = new String[][] {
            { "\"", "\"" }, // double quotes
            { "[",  "]"  }, // square brackets (a la mssql)
            { "`",  "`"  }, // backticks (a la mysql)
            { "'",  "'"  }  // single quotes
        };

        for (String[] quotePair : quotePairs) {
            String openQuote = quotePair[0];
            String closeQuote = quotePair[1];
            if (dataTypeName.startsWith(openQuote)) {
                int indexOfCloseQuote = dataTypeName.indexOf(closeQuote, openQuote.length());
                if ((indexOfCloseQuote != -1) && (dataTypeName.indexOf(closeQuote, indexOfCloseQuote + closeQuote
                    .length()) == -1)) {
                    dataTypeName = dataTypeName.substring(openQuote.length(), indexOfCloseQuote) +
                            dataTypeName.substring(indexOfCloseQuote + closeQuote.length(), dataTypeName.length());
                    break;
                }
            }
        }

        // record additional information that is still attached to the data type name
        String additionalInfo = null;
        if (dataTypeName.toLowerCase(Locale.US).startsWith("bit varying")
            || dataTypeName.toLowerCase(Locale.US).startsWith("character varying")) {
            // not going to do anything. Special case for postgres in our tests,
            // need to better support handling these types of differences
        } else {
            // Heuristic: from what we now have left of the data type name, everything after the first space
            // is counted as additional information.
            String[] splitTypeName = dataTypeName.trim().split("\\s+", 2);
            dataTypeName = splitTypeName[0];
            if (splitTypeName.length > 1) {
                additionalInfo = splitTypeName[1];
            }
        }

        // try to find matching classes for the data type name in our registry
        Collection<Class<? extends LiquibaseDataType>> classes = registry.get(dataTypeName.toLowerCase(Locale.US));

        LiquibaseDataType liquibaseDataType = null;
        if (classes == null) {
            // Map (date/time) INTERVAL types to the UnknownType
            if (dataTypeName.toUpperCase(Locale.US).startsWith("INTERVAL")) {
                liquibaseDataType = new UnknownType(dataTypeDefinition);
            } else {
                liquibaseDataType = new UnknownType(dataTypeName);
            }
        } else {
            // Iterate through the list (which is already sorted by priority) until we find a class
            // for this dataTypeName that supports the given database.
            Iterator<Class<? extends LiquibaseDataType>> iterator = classes.iterator();
            do {
                try {
                    liquibaseDataType = iterator.next().getConstructor().newInstance();
                } catch (Exception e) {
                    throw new UnexpectedLiquibaseException(e);
                }
            } while ((database != null) && !liquibaseDataType.supports(database) && iterator.hasNext());
        }
        if ((database != null) && !liquibaseDataType.supports(database)) {
            throw new UnexpectedLiquibaseException("Could not find type for " + liquibaseDataType.toString() +
                    " for DBMS "+database.getShortName());
        }
        if (liquibaseDataType == null) {
            liquibaseDataType = new UnknownType(dataTypeName);
        }
        liquibaseDataType.setAdditionalInformation(additionalInfo);

        // Does the type string have the form "some_data_type(additional,info,separated,by,commas)"?
        // If so, process these as additional data type parameters.
        if (dataTypeDefinition.matches(".+\\s*\\(.*")) {
            // Cut out the part between the first ()
            String paramStrings = dataTypeDefinition.replaceFirst(".*?\\(", "").replaceFirst("\\).*", "");
            String[] params = paramStrings.split(",");

            for (String param : params) {
                param = StringUtil.trimToNull(param);
                if (param != null) {
                    if ((liquibaseDataType instanceof CharType) && !(database instanceof OracleDatabase)) {
                        // TODO this might lead to wrong snapshot results in Oracle Database, because it assumes
                        // NLS_LENGTH_SEMANTICS=BYTE. If NLS_LENGTH_SEMANTICS=CHAR, we need to trim " CHAR" instead.
    
                        // not sure what else supports it:
                        param = param.replaceFirst(" BYTE", ""); //only use byte types on oracle,
                        
                    }
                    liquibaseDataType.addParameter(param);
                }
            }
        }

        // Did the original definition have embedded information in curly braces, e.g.
        // "int{autoIncrement:true}"? If so, we will extract and process it now.
        if (dataTypeDefinition.matches(".*\\{.*")) {
            String paramStrings = dataTypeDefinition.replaceFirst(".*?\\{", "")
                .replaceFirst("\\}.*", "");
            String[] params = paramStrings.split(",");
            for (String param : params) {
                param = StringUtil.trimToNull(param);
                if (param != null) {
                    String[] paramAndValue = param.split(":", 2);
                    // TODO: A run-time exception will occur here if the user writes a property name into the
                    // data type which does not exist - but what else could we do in this case, except aborting?
                    ObjectUtil.setProperty(liquibaseDataType, paramAndValue[0], paramAndValue[1]);
                }
            }
        }

        if (autoIncrement && (liquibaseDataType instanceof IntType)) {
            ((IntType) liquibaseDataType).setAutoIncrement(true);
        }
        if (autoIncrement && (liquibaseDataType instanceof BigIntType)) {
            ((BigIntType) liquibaseDataType).setAutoIncrement(true);
        }

        liquibaseDataType.finishInitialization(dataTypeDefinition);

        return liquibaseDataType;

    }
public LiquibaseDataType fromDescription ( String dataTypeDefinition , Database database ) { if ( dataTypeDefinition == null ) { return null ; } String dataTypeName = dataTypeDefinition ; // Remove the first occurrence of (anything within parentheses). This will remove the size information from // most data types, e.g. VARCHAR2(255 CHAR) -> VARCHAR2. We will retrieve that length information again later, // but for the moment, we are only interested in the "naked" data type name. if ( dataTypeName . matches ( ".+\\(.*\\).*" ) ) { dataTypeName = dataTypeName . replaceFirst ( "\\s*\\(.*\\)" , "" ) ; } // Remove everything { after the first opening curly bracket // e.g. int{autoIncrement:true}" -> "int" if ( dataTypeName . matches ( ".+\\{.*" ) ) { dataTypeName = dataTypeName . replaceFirst ( "\\s*\\{.*" , "" ) ; } // If the remaining string ends with " identity", then remove the " identity" and remember than we want // to set the autoIncrement property later. boolean autoIncrement = false ; if ( dataTypeName . toLowerCase ( Locale . US ) . endsWith ( " identity" ) ) { dataTypeName = dataTypeName . toLowerCase ( Locale . US ) . replaceFirst ( " identity$" , "" ) ; autoIncrement = true ; } // unquote delimited identifiers final String [ ] [ ] quotePairs = new String [ ] [ ] { { "\"" , "\"" } , // double quotes { "[" , "]" } , // square brackets (a la mssql) { "`" , "`" } , // backticks (a la mysql) { "'" , "'" } // single quotes } ; for ( String [ ] quotePair : quotePairs ) { String openQuote = quotePair [ 0 ] ; String closeQuote = quotePair [ 1 ] ; if ( dataTypeName . startsWith ( openQuote ) ) { int indexOfCloseQuote = dataTypeName . indexOf ( closeQuote , openQuote . length ( ) ) ; if ( ( indexOfCloseQuote != - 1 ) && ( dataTypeName . indexOf ( closeQuote , indexOfCloseQuote + closeQuote . length ( ) ) == - 1 ) ) { dataTypeName = dataTypeName . substring ( openQuote . length ( ) , indexOfCloseQuote ) + dataTypeName . substring ( indexOfCloseQuote + closeQuote . length ( ) , dataTypeName . length ( ) ) ; break ; } } } // record additional information that is still attached to the data type name String additionalInfo = null ; if ( dataTypeName . toLowerCase ( Locale . US ) . startsWith ( "bit varying" ) || dataTypeName . toLowerCase ( Locale . US ) . startsWith ( "character varying" ) ) { // not going to do anything. Special case for postgres in our tests, // need to better support handling these types of differences } else { // Heuristic: from what we now have left of the data type name, everything after the first space // is counted as additional information. String [ ] splitTypeName = dataTypeName . trim ( ) . split ( "\\s+" , 2 ) ; dataTypeName = splitTypeName [ 0 ] ; if ( splitTypeName . length > 1 ) { additionalInfo = splitTypeName [ 1 ] ; } } // try to find matching classes for the data type name in our registry Collection < Class < ? extends LiquibaseDataType > > classes = registry . get ( dataTypeName . toLowerCase ( Locale . US ) ) ; LiquibaseDataType liquibaseDataType = null ; if ( classes == null ) { // Map (date/time) INTERVAL types to the UnknownType if ( dataTypeName . toUpperCase ( Locale . US ) . startsWith ( "INTERVAL" ) ) { liquibaseDataType = new UnknownType ( dataTypeDefinition ) ; } else { liquibaseDataType = new UnknownType ( dataTypeName ) ; } } else { // Iterate through the list (which is already sorted by priority) until we find a class // for this dataTypeName that supports the given database. Iterator < Class < ? extends LiquibaseDataType > > iterator = classes . iterator ( ) ; do { try { liquibaseDataType = iterator . next ( ) . getConstructor ( ) . newInstance ( ) ; } catch ( Exception e ) { throw new UnexpectedLiquibaseException ( e ) ; } } while ( ( database != null ) && ! liquibaseDataType . supports ( database ) && iterator . hasNext ( ) ) ; } if ( ( database != null ) && ! liquibaseDataType . supports ( database ) ) { throw new UnexpectedLiquibaseException ( "Could not find type for " + liquibaseDataType . toString ( ) + " for DBMS " + database . getShortName ( ) ) ; } if ( liquibaseDataType == null ) { liquibaseDataType = new UnknownType ( dataTypeName ) ; } liquibaseDataType . setAdditionalInformation ( additionalInfo ) ; // Does the type string have the form "some_data_type(additional,info,separated,by,commas)"? // If so, process these as additional data type parameters. if ( dataTypeDefinition . matches ( ".+\\s*\\(.*" ) ) { // Cut out the part between the first () String paramStrings = dataTypeDefinition . replaceFirst ( ".*?\\(" , "" ) . replaceFirst ( "\\).*" , "" ) ; String [ ] params = paramStrings . split ( "," ) ; for ( String param : params ) { param = StringUtil . trimToNull ( param ) ; if ( param != null ) { if ( ( liquibaseDataType instanceof CharType ) && ! ( database instanceof OracleDatabase ) ) { // TODO this might lead to wrong snapshot results in Oracle Database, because it assumes // NLS_LENGTH_SEMANTICS=BYTE. If NLS_LENGTH_SEMANTICS=CHAR, we need to trim " CHAR" instead. // not sure what else supports it: param = param . replaceFirst ( " BYTE" , "" ) ; //only use byte types on oracle, } liquibaseDataType . addParameter ( param ) ; } } } // Did the original definition have embedded information in curly braces, e.g. // "int{autoIncrement:true}"? If so, we will extract and process it now. if ( dataTypeDefinition . matches ( ".*\\{.*" ) ) { String paramStrings = dataTypeDefinition . replaceFirst ( ".*?\\{" , "" ) . replaceFirst ( "\\}.*" , "" ) ; String [ ] params = paramStrings . split ( "," ) ; for ( String param : params ) { param = StringUtil . trimToNull ( param ) ; if ( param != null ) { String [ ] paramAndValue = param . split ( ":" , 2 ) ; // TODO: A run-time exception will occur here if the user writes a property name into the // data type which does not exist - but what else could we do in this case, except aborting? ObjectUtil . setProperty ( liquibaseDataType , paramAndValue [ 0 ] , paramAndValue [ 1 ] ) ; } } } if ( autoIncrement && ( liquibaseDataType instanceof IntType ) ) { ( ( IntType ) liquibaseDataType ) . setAutoIncrement ( true ) ; } if ( autoIncrement && ( liquibaseDataType instanceof BigIntType ) ) { ( ( BigIntType ) liquibaseDataType ) . setAutoIncrement ( true ) ; } liquibaseDataType . finishInitialization ( dataTypeDefinition ) ; return liquibaseDataType ; }
Translates a column data type definition (e.g. varchar(255), java.sql.Types.NVARCHAR(10),
VARCHAR2(255 BYTE)... ) into a normalized data type in object form. Note that, due to variety of allowed ways
to specify a data type (SQL-Standard, Java type, native RDBMS type...), the dataTypeDefinition we receive for
processing may already be the native type for the target RDBMS.
@param dataTypeDefinition the definition from the changeSet
@param database the {@link Database} object from for which the native definition is to be generated
@return the corresponding Liquibase data type in object form.
Translates a column data type definition ( e . g . varchar ( 255 ) java . sql . Types . NVARCHAR ( 10 ) VARCHAR2 ( 255 BYTE ) ... ) into a normalized data type in object form . Note that due to variety of allowed ways to specify a data type ( SQL - Standard Java type native RDBMS type ... ) the dataTypeDefinition we receive for processing may already be the native type for the target RDBMS .