package liquibase.database.core;

import liquibase.CatalogAndSchema;
import liquibase.Scope;
import liquibase.changelog.column.LiquibaseColumn;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.ObjectQuotingStrategy;
import liquibase.database.jvm.JdbcConnection;
import liquibase.logging.Logger;
import liquibase.structure.DatabaseObject;
import liquibase.exception.DatabaseException;
import liquibase.logging.LogService;
import liquibase.logging.LogType;
import liquibase.statement.SqlStatement;
import liquibase.statement.core.RawCallStatement;
import liquibase.structure.core.Table;
import liquibase.util.JdbcUtils;
import liquibase.util.StringUtil;

import java.math.BigInteger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;

/**
 * Encapsulates PostgreSQL database support.
 */
public class PostgresDatabase extends AbstractJdbcDatabase {
    public static final String PRODUCT_NAME = "PostgreSQL";
    public static final int MINIMUM_DBMS_MAJOR_VERSION = 9;
    public static final int MINIMUM_DBMS_MINOR_VERSION = 2;
    private static final int PGSQL_DEFAULT_TCP_PORT_NUMBER = 5432;
    private static final Logger LOG = Scope.getCurrentScope().getLog(PostgresDatabase.class);

    private Set<String> systemTablesAndViews = new HashSet<>();

    private Set<String> reservedWords = new HashSet<>();

    public PostgresDatabase() {
        super.setCurrentDateTimeFunction("NOW()");
        // "Reserved" or "reserved (can be function or type)" in PostgreSQL
        // from https://www.postgresql.org/docs/9.6/static/sql-keywords-appendix.html
        reservedWords.addAll(Arrays.asList("ALL", "ANALYSE", "ANALYZE", "AND", "ANY", "ARRAY", "AS", "ASC",
                "ASYMMETRIC", "AUTHORIZATION", "BINARY", "BOTH", "CASE", "CAST", "CHECK", "COLLATE", "COLLATION",
                "COLUMN", "CONCURRENTLY", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_CATALOG", "CURRENT_DATE",
                "CURRENT_ROLE", "CURRENT_SCHEMA", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "DEFAULT",
                "DEFERRABLE", "DESC", "DISTINCT", "DO", "ELSE", "END", "EXCEPT", "FALSE", "FETCH", "FOR", "FOREIGN",
                "FREEZE", "FROM", "FULL", "GRANT", "GROUP", "HAVING", "ILIKE", "IN", "INITIALLY", "INNER", "INTERSECT",
                "INTO", "IS", "ISNULL", "JOIN", "LATERAL", "LEADING", "LEFT", "LIKE", "LIMIT", "LOCALTIME",
                "LOCALTIMESTAMP", "NATURAL", "NOT", "NOTNULL", "NULL", "OFFSET", "ON", "ONLY", "OR", "ORDER", "OUTER",
                "OVERLAPS", "PLACING", "PRIMARY", "REFERENCES", "RETURNING", "RIGHT", "SELECT", "SESSION_USER",
                "SIMILAR", "SOME", "SYMMETRIC", "TABLE", "TABLESAMPLE", "THEN", "TO", "TRAILING", "TRUE", "UNION",
                "UNIQUE", "USER", "USING", "VARIADIC", "VERBOSE", "WHEN", "WHERE", "WINDOW", "WITH"));
        super.sequenceNextValueFunction = "nextval('%s')";
        super.sequenceCurrentValueFunction = "currval('%s')";
        super.unmodifiableDataTypes.addAll(Arrays.asList("bool", "int4", "int8", "float4", "float8", "bigserial", "serial", "oid", "bytea", "date", "timestamptz", "text"));
        super.unquotedObjectsAreUppercased=false;
    }

    @Override
    public boolean equals(Object o) {
        // Actually, we don't need and more specific checks than the base method. This exists just to make SONAR happy.
        return super.equals(o);
    }

    @Override
    public int hashCode() {
        // Actually, we don't need and more specific hashing than the base method. This exists just to make SONAR happy.
        return super.hashCode();
    }

    @Override
    public String getShortName() {
        return "postgresql";
    }

    @Override
    protected String getDefaultDatabaseProductName() {
        return "PostgreSQL";
    }

    @Override
    public Integer getDefaultPort() {
        return PGSQL_DEFAULT_TCP_PORT_NUMBER;
    }

    @Override
    public Set<String> getSystemViews() {
        return systemTablesAndViews;
    }

    @Override
    public int getPriority() {
        return PRIORITY_DEFAULT;
    }

    @Override
    public boolean supportsInitiallyDeferrableColumns() {
        return true;
    }

    @Override
    public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
        if (!PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName())) {
            return false;
        }

        int majorVersion = conn.getDatabaseMajorVersion();
        int minorVersion =conn.getDatabaseMinorVersion();

        if ((majorVersion < MINIMUM_DBMS_MAJOR_VERSION) || ((majorVersion == MINIMUM_DBMS_MAJOR_VERSION) &&
            (minorVersion < MINIMUM_DBMS_MINOR_VERSION))) {
            LOG.warning(
                String.format(
                    "Your PostgreSQL software version (%d.%d) seems to indicate that your software is " +
                        "older than %d.%d. This means that you might encounter strange behaviour and " +
                        "incorrect error messages.",
                    majorVersion, minorVersion, majorVersion, minorVersion));
            return true;
        }

        return true;
    }

    @Override
    public String getDefaultDriver(String url) {
        if (url.startsWith("jdbc:postgresql:")) {
            return "org.postgresql.Driver";
        }
        return null;
    }

    @Override
    public boolean supportsCatalogInObjectName(Class<? extends DatabaseObject> type) {
        return false;
    }

    @Override
    public boolean supportsSequences() {
        return true;
    }

    @Override
    public String getDatabaseChangeLogTableName() {
        return super.getDatabaseChangeLogTableName().toLowerCase(Locale.US);
    }

    @Override
    public String getDatabaseChangeLogLockTableName() {
        return super.getDatabaseChangeLogLockTableName().toLowerCase(Locale.US);
    }

    @Override
    public void setConnection(DatabaseConnection conn) {
        super.setConnection(conn);

        if (conn instanceof JdbcConnection) {
            Statement statement = null;
            ResultSet resultSet = null;
            try {
                statement = ((JdbcConnection) conn).createStatement();
                resultSet = statement.executeQuery("select setting from pg_settings where name = 'edb_redwood_date'");
                if (resultSet.next()) {
                    String setting = resultSet.getString(1);
                    if ((setting != null) && "on".equals(setting)) {
                        LOG.warning(LogType.LOG, "EnterpriseDB " + conn.getURL() + " does not store DATE columns. Instead, it auto-converts " +
                                "them " +
                                "to TIMESTAMPs. (edb_redwood_date=true)");
                    }
                }
            } catch (SQLException | DatabaseException e) {
                LOG.info(LogType.LOG, "Cannot check pg_settings", e);
            } finally {
                JdbcUtils.close(resultSet, statement);
            }
        }

    }

    @Override
    public boolean isSystemObject(DatabaseObject example) {
        // All tables in the schemas pg_catalog and pg_toast are definitely system tables.
        if
                (
                (example instanceof Table)
                        && (example.getSchema() != null)
                        && (
                        ("pg_catalog".equals(example.getSchema().getName()))
                                || ("pg_toast".equals(example.getSchema().getName()))
                )
                ) {
            return true;
        }

        return super.isSystemObject(example);
    }

    @Override
    public boolean supportsTablespaces() {
        return true;
    }

    @Override
    public String getAutoIncrementClause() {
        return "";
    }

    @Override
    public boolean generateAutoIncrementStartWith(BigInteger startWith) {
        return false;
    }

    @Override
    public boolean generateAutoIncrementBy(BigInteger incrementBy) {
        return false;
    }

    /**
     * This has special case logic to handle NOT quoting column names if they are
     * of type 'LiquibaseColumn' - columns in the DATABASECHANGELOG or DATABASECHANGELOGLOCK
     * tables.
     */
    @Override
    public String escapeObjectName(String objectName, Class<? extends DatabaseObject> objectType) {
        if ((quotingStrategy == ObjectQuotingStrategy.LEGACY) && hasMixedCase(objectName)) {
            return "\"" + objectName + "\"";
        } else if (objectType != null && LiquibaseColumn.class.isAssignableFrom(objectType)) {
            return (objectName != null && !objectName.isEmpty()) ? objectName.trim() : objectName;
        }

        return super.escapeObjectName(objectName, objectType);
    }

    @Override
    public String correctObjectName(String objectName, Class<? extends DatabaseObject> objectType) {
        if ((objectName == null) || (quotingStrategy != ObjectQuotingStrategy.LEGACY)) {
            return super.correctObjectName(objectName, objectType);
        }
        if (objectName.contains("-")
                || hasMixedCase(objectName)
                || startsWithNumeric(objectName)
                || isReservedWord(objectName)) {
            return objectName;
        } else {
            return objectName.toLowerCase(Locale.US);
        }
    }

    /*
    * Check if given string has case problems according to postgresql documentation.
    * If there are at least one characters with upper case while all other are in lower case (or vice versa) this
    * string should be escaped.
    *
    * Note: This may make postgres support more case sensitive than normally is, but needs to be left in for backwards
    * compatibility.
    * Method is public so a subclass extension can override it to always return false.
    */
    protected boolean hasMixedCase(String tableName) {
        if (tableName == null) {
            return false;
        }
        return StringUtil.hasUpperCase(tableName) && StringUtil.hasLowerCase(tableName);
    }

    @Override
    public boolean isReservedWord(String tableName) {
        return reservedWords.contains(tableName.toUpperCase());
    }

    @Override
    protected SqlStatement getConnectionSchemaNameCallStatement() {
        return new RawCallStatement("select current_schema()");
    }

    @Override
    public String generatePrimaryKeyName(final String tableName) {
        return tableName.toUpperCase(Locale.US) + "_PKEY";
    }

    @Override
    @SuppressWarnings("squid:S109")
    public int getMaxFractionalDigitsForTimestamp() {

        int major = 0;
        int minor = 0;

        try {
            major = getDatabaseMajorVersion();
            minor = getDatabaseMinorVersion();
        } catch (DatabaseException x) {
            Scope.getCurrentScope().getLog(getClass()).warning(
                    LogType.LOG, "Unable to determine exact database server version"
                            + " - specified TIMESTAMP precision"
                            + " will not be set: ", x);
            return 0;
        }

        // PostgreSQL 7.2 introduced fractional support...
        // https://www.postgresql.org/docs/9.2/static/datatype-datetime.html
        String minimumVersion = "7.2";

        if (StringUtil.isMinimumVersion(minimumVersion, major, minor, 0)) {
            return 6;
        } else {
            return 0;
        }
    }

    @Override
    public CatalogAndSchema.CatalogAndSchemaCase getSchemaAndCatalogCase() {
        return CatalogAndSchema.CatalogAndSchemaCase.LOWER_CASE;
    }
}


package liquibase.change;

import liquibase.exception.DateParseException;
import liquibase.parser.core.ParsedNode;
import liquibase.parser.core.ParsedNodeException;
import liquibase.resource.ResourceAccessor;
import liquibase.serializer.AbstractLiquibaseSerializable;
import liquibase.serializer.ReflectionSerializer;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.NotNullConstraint;
import liquibase.statement.SequenceCurrentValueFunction;
import liquibase.statement.SequenceNextValueFunction;
import liquibase.structure.core.Column;
import liquibase.structure.core.ForeignKey;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Table;
import liquibase.structure.core.UniqueConstraint;
import liquibase.util.ISODateFormat;
import liquibase.util.ObjectUtil;
import liquibase.util.StringUtil;
import liquibase.util.NowAndTodayUtil;

import java.math.BigInteger;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * The standard configuration used by Change classes to represent a column.
 * It is not required that a column-based Change uses this class, but parsers should look for it so it is a helpful
 * convenience. The definitions of "defaultValue" and "value" will vary based on the Change and may not be applicable
 * in all cases.
 */
public class ColumnConfig extends AbstractLiquibaseSerializable {
    private String name;
    private Boolean computed;
    private String type;
    private String value;
    private Number valueNumeric;
    private Date valueDate;
    private Boolean valueBoolean;
    private String valueBlobFile;
    private String valueClobFile;
    private String encoding;
    private DatabaseFunction valueComputed;
    private SequenceNextValueFunction valueSequenceNext;
    private SequenceCurrentValueFunction valueSequenceCurrent;

    private String defaultValue;
    private Number defaultValueNumeric;
    private Date defaultValueDate;
    private Boolean defaultValueBoolean;
    private DatabaseFunction defaultValueComputed;
    private SequenceNextValueFunction defaultValueSequenceNext;
    private String defaultValueConstraintName;

    private ConstraintsConfig constraints;
    private Boolean autoIncrement;
    private BigInteger startWith;
    private BigInteger incrementBy;
    private String remarks;
    private Boolean descending;

    /**
     * Create a ColumnConfig object based on a {@link Column} snapshot.
     * It will attempt to set as much as possible based on the information in the snapshot.
     */
    public ColumnConfig(Column columnSnapshot) {
        setName(columnSnapshot.getName());
        setComputed(((columnSnapshot.getComputed() != null) && columnSnapshot.getComputed()) ? Boolean.TRUE : null);
        setDescending(((columnSnapshot.getDescending() != null) && columnSnapshot.getDescending()) ? Boolean.TRUE : null);
        if (columnSnapshot.getType() != null) {
            setType(columnSnapshot.getType().toString());
        }

        if (columnSnapshot.getDefaultValue() != null) {
            Object defaultValue = columnSnapshot.getDefaultValue();
            if (defaultValue instanceof Boolean) {
                setDefaultValueBoolean((Boolean) defaultValue);
            } else if (defaultValue instanceof Number) {
                setDefaultValueNumeric(defaultValue.toString());
            } else if (defaultValue instanceof SequenceNextValueFunction) {
                setDefaultValueSequenceNext((SequenceNextValueFunction) defaultValue);
            } else if (defaultValue instanceof DatabaseFunction) {
                setDefaultValueComputed((DatabaseFunction) defaultValue);
            } else if (defaultValue instanceof Date) {
                setDefaultValueDate((Date) defaultValue);
            } else {
                setDefaultValue(defaultValue.toString());
            }
        }
        setDefaultValueConstraintName(columnSnapshot.getDefaultValueConstraintName());

        boolean nonDefaultConstraints = false;
        ConstraintsConfig constraints = new ConstraintsConfig();

        if ((columnSnapshot.isNullable() != null) && !columnSnapshot.isNullable()) {
            constraints.setNullable(columnSnapshot.isNullable());
            nonDefaultConstraints = true;
        }

        if ((columnSnapshot.getRelation() != null) && (columnSnapshot.getRelation() instanceof Table)) {
            Table table = (Table) columnSnapshot.getRelation();
            List<NotNullConstraint> notNullConstraints = table.getNotNullConstraints();
            if (notNullConstraints != null) {
                    for (NotNullConstraint constraint : notNullConstraints) {
                            if (constraint.getColumnName().equals(getName())) {
                                    constraints.setNullable(false);
                                    constraints.setNotNullConstraintName(constraint.getName());
                                    nonDefaultConstraints = true;
                                }
                        }
                }

            if (columnSnapshot.isAutoIncrement()) {
                setAutoIncrement(true);
                setStartWith(columnSnapshot.getAutoIncrementInformation().getStartWith());
                setIncrementBy(columnSnapshot.getAutoIncrementInformation().getIncrementBy());
            } else {
                setAutoIncrement(false);
            }

            PrimaryKey primaryKey = table.getPrimaryKey();
            if ((primaryKey != null) && primaryKey.getColumnNamesAsList().contains(columnSnapshot.getName())) {
                constraints.setPrimaryKey(true);
                constraints.setPrimaryKeyName(primaryKey.getName());
                constraints.setPrimaryKeyTablespace(primaryKey.getTablespace());
                nonDefaultConstraints = true;
            }

            List<UniqueConstraint> uniqueConstraints = table.getUniqueConstraints();
            if (uniqueConstraints != null) {
                for (UniqueConstraint constraint : uniqueConstraints) {
                    if (constraint.getColumnNames().contains(getName())) {
                        constraints.setUnique(true);
                        constraints.setUniqueConstraintName(constraint.getName());
                        nonDefaultConstraints = true;
                    }
                }
            }

            List<ForeignKey> fks = table.getOutgoingForeignKeys();
            if (fks != null) {
                for (ForeignKey fk : fks) {
                    if ((fk.getForeignKeyColumns() != null) && (fk.getForeignKeyColumns().size() == 1) && fk
                        .getForeignKeyColumns().get(0).getName().equals(getName())) {
                        constraints.setForeignKeyName(fk.getName());
                        constraints.setReferences(fk.getPrimaryKeyTable().getName() +
                            "(" +
                            fk.getPrimaryKeyColumns().get(0).getName() +
                            ")");
                        nonDefaultConstraints = true;
                    }
                }
            }

            if (nonDefaultConstraints) {
                setConstraints(constraints);
            }
        }

        setRemarks(columnSnapshot.getRemarks());
    }

    /**
     * Create am empty ColumnConfig object. Boolean and other object values will default to null.
     */
    public ColumnConfig() {
    }

    public static ColumnConfig fromName(String name) {
        name = name.trim();
        Boolean descending = null;
        if (name.matches("(?i).*\\s+DESC")) {
            name = name.replaceFirst("(?i)\\s+DESC$", "");
            descending = true;
        } else if (name.matches("(?i).*\\s+ASC")) {
            name = name.replaceFirst("(?i)\\s+ASC$", "");
            descending = false;
        }
        return new ColumnConfig()
                .setName(name)
                .setDescending(descending);
    }

    public static ColumnConfig[] arrayFromNames(String names) {
        if (names == null) {
            return null;
        }
        List<String> nameArray = StringUtil.splitAndTrim(names, ",");
        ColumnConfig[] returnArray = new ColumnConfig[nameArray.size()];
        for (int i = 0; i < nameArray.size(); i++) {
            returnArray[i] = fromName(nameArray.get(i));
        }
        return returnArray;
    }

    /**
     * The name of the column.
     */
    public String getName() {
        return name;
    }

    public ColumnConfig setName(String name) {
        this.name = name;
        return this;
    }

    public ColumnConfig setName(String name, boolean computed) {
        setComputed(computed);
        return setName(name);
    }

    public Boolean getComputed() {
        return computed;
    }

    public ColumnConfig setComputed(Boolean computed) {
        this.computed = computed;
        return this;
    }

    /**
     * The data type fof the column.
     * This value will pass through {@link liquibase.datatype.DataTypeFactory#fromDescription(String, liquibase.database.Database)} before being included in SQL.
     */
    public String getType() {
        return type;
    }

    public ColumnConfig setType(String type) {
        this.type = type;
        return this;
    }

    /**
     * The String value to set this column to. If you do not want the value set by {@link #setValue(String)}
     * use a more specific function like {@link #getValueNumeric()} or the more generic {@link #getValueObject()}
     *

     * If performing an data manipulation operation, the setValue* functions should be used to set what the columns
     * should be set to. If performing a data definition operation, this setValue* functions should be used to set
     * what existing rows should be set to (may be different than the default value for new rows)
     */
    public String getValue() {
        return value;
    }

    /**
     * Sets the string value this column should be set to.
     * If you are trying to set a value type other than a string, use the more specific functions like {@link #setValueNumeric(Number)}.
     * This method does no processing of the string. Any trimming is expected to be done beforehand. It does not conver the string "null" to null
     * so that you can set the string "null" as a value if you are feeling particularly crazy.
     */
    public ColumnConfig setValue(String value) {
        this.value = value;

        return this;
    }

    /**
     * Return the numeric value this column should be set to.
     * @see #setValue(String)
     */
    public Number getValueNumeric() {
        return valueNumeric;
    }

    public ColumnConfig setValueNumeric(Number valueNumeric) {
        this.valueNumeric = valueNumeric;

        return this;
    }

    /**
     * Set the number this column should be set to. Supports integers and decimals, and strips off any wrapping
     * parentheses. If the passed value cannot be parsed as a number in US locale, it is assumed to be a function
     * that returns a number. If the value "null" is passed, it will set a null value.
     */
    public ColumnConfig setValueNumeric(String valueNumeric) {
        if ((valueNumeric == null) || "null".equalsIgnoreCase(valueNumeric)) {
            this.valueNumeric = null;
        } else {
            String saved = valueNumeric;
            if (valueNumeric.startsWith("(")) {
                valueNumeric = valueNumeric.replaceFirst("^\\(", "");
                valueNumeric = valueNumeric.replaceFirst("\\)$", "");
            }

            try {
                this.valueNumeric = ValueNumeric.of(Locale.US, valueNumeric);
            } catch (ParseException e) {
                this.valueComputed = new DatabaseFunction(saved);
            }
        }

        return this;
    }

    /**
     * Return the boolean value this column should be set to.
     * @see #setValue(String)
     */
    public Boolean getValueBoolean() {
        return valueBoolean;
    }

    /**
     * Set the valueBoolean based on a given string.
     * If the passed value cannot be parsed as a date, it is assumed to be a function that returns a boolean.
     * If the string "null" or an empty string is passed, it will set a null value.
     * If "1" is passed, defaultValueBoolean is set to true. If 0 is passed, defaultValueBoolean is set to false
     */
    public ColumnConfig setValueBoolean(String valueBoolean) {
        valueBoolean = StringUtil.trimToNull(valueBoolean);
        if ((valueBoolean == null) || "null".equalsIgnoreCase(valueBoolean)) {
            this.valueBoolean = null;
        } else {
            if ("true".equalsIgnoreCase(valueBoolean) || "1".equals(valueBoolean)) {
                this.valueBoolean = true;
            } else if ("false".equalsIgnoreCase(valueBoolean) || "0".equals(valueBoolean)) {
                this.valueBoolean = false;
            } else {
                this.valueComputed = new DatabaseFunction(valueBoolean);
            }

        }

        return this;
    }

    public ColumnConfig setValueBoolean(Boolean valueBoolean) {
        this.valueBoolean = valueBoolean;

        return this;
    }

    /**
     * Return the function this column should be set from.
     * @see #setValue(String)
     */

    public DatabaseFunction getValueComputed() {
        return valueComputed;
    }

    public ColumnConfig setValueComputed(DatabaseFunction valueComputed) {
        this.valueComputed = valueComputed;

        return this;
    }

    public SequenceNextValueFunction getValueSequenceNext() {
        return valueSequenceNext;
    }

    public ColumnConfig setValueSequenceNext(SequenceNextValueFunction valueSequenceNext) {
        this.valueSequenceNext = valueSequenceNext;

        return this;
    }

    public SequenceCurrentValueFunction getValueSequenceCurrent() {
        return valueSequenceCurrent;
    }

    public ColumnConfig setValueSequenceCurrent(SequenceCurrentValueFunction valueSequenceCurrent) {
        this.valueSequenceCurrent = valueSequenceCurrent;

        return this;
    }

    /**
     * Return the date value this column should be set to.
     * @see #setValue(String)
     */
    public Date getValueDate() {
        return valueDate;
    }

    /**
     * Set the date this column should be set to. Supports any of the date or datetime formats handled by {@link ISODateFormat}.
     * If the passed value cannot be parsed as a date, it is assumed to be a function that returns a date.
     * If the string "null" is passed, it will set a null value.
     * @param valueDate the Date Value to use (may be null or "null", or start with "now" or "today".
     * @throws DateParseException if the columnType isn't supported for "now" or "today" values.
     */
    public ColumnConfig setValueDate(String valueDate) throws DateParseException {
        if ((valueDate == null) || "null".equalsIgnoreCase(valueDate)) {
            this.valueDate = null;
        } else if (NowAndTodayUtil.isNowOrTodayFormat(valueDate)) {
            this.valueDate = NowAndTodayUtil.doNowOrToday(valueDate, this.getType());
        } else {
            try {
                this.valueDate = new ISODateFormat().parse(valueDate);
            } catch (ParseException e) {
                //probably a function
                this.valueComputed = new DatabaseFunction(valueDate);
            }
        }

        return this;
    }

    public ColumnConfig setValueDate(Date valueDate) {
        this.valueDate = valueDate;

        return this;
    }

    /**
     * Return the file containing the data to load into a BLOB.
     * @see #setValue(String)
     */
    public String getValueBlobFile() {
        return valueBlobFile;
    }

    public ColumnConfig setValueBlobFile(String valueBlobFile) {
        this.valueBlobFile = valueBlobFile;
        return this;
    }

    /**
     * Return the file containing the data to load into a CLOB.
     * @see #setValue(String)
     */
    public String getValueClobFile() {
        return valueClobFile;
    }

    public ColumnConfig setValueClobFile(String valueClobFile) {
        this.valueClobFile = valueClobFile;
        return this;
    }

    /**
     * Return encoding of a file, referenced via {@link #valueClobFile}.
     */
    public String getEncoding() {
        return encoding;
    }

    public ColumnConfig setEncoding(String encoding) {
        this.encoding = encoding;
        return this;
    }

    /**
     * Return the value from whatever setValue* function was called. Will return null if none were set.
     */
    public Object getValueObject() {
        if (getValue() != null) {
            return getValue();
        } else if (getValueBoolean() != null) {
            return getValueBoolean();
        } else if (getValueNumeric() != null) {
            return getValueNumeric();
        } else if (getValueDate() != null) {
            return getValueDate();
        } else if (getValueComputed() != null) {
            return getValueComputed();
        } else if (getValueClobFile() != null) {
            return getValueClobFile();
        } else if (getValueBlobFile() != null) {
            return getValueBlobFile();
        } else if (getValueSequenceNext() != null) {
            return getValueSequenceNext();
        } else if (getValueSequenceCurrent() != null) {
            return getValueSequenceCurrent();
        }
        return null;
    }

    /**
     * The String default value to assign to this column. If you do not want the default set by {@link #setDefaultValue(String)}
     * use a more specific function like {@link #getDefaultValueNumeric()} or the more generic {@link #getDefaultValueObject()}
     */
    public String getDefaultValue() {
        return defaultValue;
    }

    /**
     * Sets the string default value to assign to this column. If you are trying to set a default value type other than a string, use the more specific functions
     * like {@link #setDefaultValueNumeric(Number)}.
     * This method does no processing of the string. Any trimming is expected to be done beforehand. It does not convert the string "null" to null
     * so that you can set the string "null" as a value if you are feeling particularly crazy.
     */
    public ColumnConfig setDefaultValue(String defaultValue) {
        this.defaultValue = defaultValue;

        return this;
    }

    /**
     * Return the numeric value this column should default to.
     * @see #setDefaultValue(String)
     */
    public Number getDefaultValueNumeric() {
        return defaultValueNumeric;
    }

    /**
     * Set the number this column should use as its default. Supports integers and decimals, and strips off any wrapping parentheses.
     * If the passed value cannot be parsed as a number, it is assumed to be a function that returns a number.
     * If the value "null" is passed, it will set a null value.
     *

     * A special case is made for "GENERATED_BY_DEFAULT" which simply sets the ColumnConfig object to autoIncrement.
     */
    public ColumnConfig setDefaultValueNumeric(String defaultValueNumeric) {
        if ((defaultValueNumeric == null) || "null".equalsIgnoreCase(defaultValueNumeric)) {
            this.defaultValueNumeric = null;
        } else {
            if ("GENERATED_BY_DEFAULT".equals(defaultValueNumeric)) {
                setAutoIncrement(true);
            } else {
                if (defaultValueNumeric.startsWith("(")) {
                    defaultValueNumeric = defaultValueNumeric.replaceFirst("^\\(", "");
                    defaultValueNumeric = defaultValueNumeric.replaceFirst("\\)$", "");
                }
                try {
                    this.defaultValueNumeric = ValueNumeric.of(Locale.US, defaultValueNumeric);
                } catch (ParseException e) {
                    this.defaultValueComputed = new DatabaseFunction(defaultValueNumeric);
                }
            }
        }

        return this;
    }

    public ColumnConfig setDefaultValueNumeric(Number defaultValueNumeric) {
        this.defaultValueNumeric = defaultValueNumeric;

        return this;
    }

    /**
     * Return the date value this column should default to.
     * @see #setDefaultValue(String)
     */
    public Date getDefaultValueDate() {
        return defaultValueDate;
    }

    public ColumnConfig setDefaultValueDate(Date defaultValueDate) {
        this.defaultValueDate = defaultValueDate;

        return this;
    }

    /**
     * Set the date this column should default to. Supports any of the date or datetime formats handled by {@link ISODateFormat}.
     * If the passed value cannot be parsed as a date, it is assumed to be a function that returns a date.
     * If the string "null" or an empty string is passed, it will set a null value.
     */
    public ColumnConfig setDefaultValueDate(String defaultValueDate) {
        defaultValueDate = StringUtil.trimToNull(defaultValueDate);
        if ((defaultValueDate == null) || "null".equalsIgnoreCase(defaultValueDate)) {
            this.defaultValueDate = null;
        } else {
            try {
                this.defaultValueDate = new ISODateFormat().parse(defaultValueDate);
            } catch (ParseException e) {
                //probably a computed date
                this.defaultValueComputed = new DatabaseFunction(defaultValueDate);
            }
        }

        return this;
    }

    /**
     * Return the boolean value this column should default to.
     * @see #setDefaultValue(String)
     */
    public Boolean getDefaultValueBoolean() {
        return defaultValueBoolean;
    }

    /**
     * Set the defaultValueBoolean based on a given string.
     * If the passed value cannot be parsed as a date, it is assumed to be a function that returns a boolean.
     * If the string "null" or an empty string is passed, it will set a null value.
     * If "1" is passed, defaultValueBoolean is set to true. If 0 is passed, defaultValueBoolean is set to false
     */
    public ColumnConfig setDefaultValueBoolean(String defaultValueBoolean) {
        defaultValueBoolean = StringUtil.trimToNull(defaultValueBoolean);
        if ((defaultValueBoolean == null) || "null".equalsIgnoreCase(defaultValueBoolean)) {
            this.defaultValueBoolean = null;
        } else {
            if ("true".equalsIgnoreCase(defaultValueBoolean) || "1".equals(defaultValueBoolean)) {
                this.defaultValueBoolean = true;
            } else if ("false".equalsIgnoreCase(defaultValueBoolean) || "0".equals(defaultValueBoolean)) {
                this.defaultValueBoolean = false;
            } else {
                this.defaultValueComputed = new DatabaseFunction(defaultValueBoolean);
            }

        }

        return this;
    }

    public ColumnConfig setDefaultValueBoolean(Boolean defaultValueBoolean) {
        this.defaultValueBoolean = defaultValueBoolean;

        return this;
    }

    /**
     * Return the function whose value should generate this column's default.
     * @see #setDefaultValue(String)
     */
    public DatabaseFunction getDefaultValueComputed() {
        return defaultValueComputed;
    }

    public ColumnConfig setDefaultValueComputed(DatabaseFunction defaultValueComputed) {
        this.defaultValueComputed = defaultValueComputed;

        return this;
    }

    /**
     * Return the value to set this column's default to according to the setDefaultValue* function that was called.
     * If none were called, this function returns null.
     */
    public Object getDefaultValueObject() {
        if (getDefaultValue() != null) {
            return getDefaultValue();
        } else if (getDefaultValueBoolean() != null) {
            return getDefaultValueBoolean();
        } else if (getDefaultValueNumeric() != null) {
            return getDefaultValueNumeric();
        } else if (getDefaultValueDate() != null) {
            return getDefaultValueDate();
        } else if (getDefaultValueComputed() != null) {
            return getDefaultValueComputed();
        } else if (getDefaultValueSequenceNext() != null) {
            return getDefaultValueSequenceNext();
        }
        return null;
    }

    /**
     * Returns the ConstraintsConfig this ColumnConfig is using. Returns null if nho constraints have been assigned yet.
     */
    public ConstraintsConfig getConstraints() {
        return constraints;
    }

    public ColumnConfig setConstraints(ConstraintsConfig constraints) {
        this.constraints = constraints;

        return this;
    }

    /**
     * Returns true if this Column should be set to be auto increment. Returns null if auto-increment hasn't been explicitly assigned.
     */
    public Boolean isAutoIncrement() {
        return autoIncrement;
    }

    public ColumnConfig setAutoIncrement(Boolean autoIncrement) {
        this.autoIncrement = autoIncrement;

        return this;
    }

    /**
     * Return the number to start auto incrementing with.
     */
    public BigInteger getStartWith() {
        return startWith;
    }

    public ColumnConfig setStartWith(BigInteger startWith) {
        this.startWith = startWith;

        return this;
    }

    /**
     * Return the amount to auto increment by.
     */
    public BigInteger getIncrementBy() {
        return incrementBy;
    }

    public ColumnConfig setIncrementBy(BigInteger incrementBy) {
        this.incrementBy = incrementBy;

        return this;
    }

    /**
     * Returns true if any of the setDefaultValue* functions have had a non-null value set
     */
    public boolean hasDefaultValue() {
        return (this.getDefaultValue() != null) || (this.getDefaultValueBoolean() != null) || (this
            .getDefaultValueDate() != null) || (this.getDefaultValueNumeric() != null) || (this
            .getDefaultValueComputed() != null) || (this.getDefaultValueSequenceNext() != null);
    }

    /**
     * Return the remarks to apply to this column.
     */
    public String getRemarks() {
        return remarks;
    }

    public ColumnConfig setRemarks(String remarks) {
        this.remarks = remarks;
        return this;
    }

    public Boolean getDescending() {
        return descending;
    }

    public ColumnConfig setDescending(Boolean descending) {
        this.descending = descending;
        return this;
    }

    @Override
    public String getSerializedObjectName() {
        return "column";
    }

    public SequenceNextValueFunction getDefaultValueSequenceNext() {
        return defaultValueSequenceNext;
    }

    public ColumnConfig setDefaultValueSequenceNext(SequenceNextValueFunction defaultValueSequenceNext) {
        this.defaultValueSequenceNext = defaultValueSequenceNext;

        return this;
    }

    public String getDefaultValueConstraintName() {
        return defaultValueConstraintName;
    }

    public void setDefaultValueConstraintName(String defaultValueConstraintName) {
        this.defaultValueConstraintName = defaultValueConstraintName;
    }

    @Override
    public SerializationType getSerializableFieldType(String field) {
        return SerializationType.NAMED_FIELD;
    }

    @Override
    public String getSerializedObjectNamespace() {
        return STANDARD_CHANGELOG_NAMESPACE;
    }

    @Override
    public void load(ParsedNode parsedNode, ResourceAccessor resourceAccessor) throws ParsedNodeException {
        for (ParsedNode child : parsedNode.getChildren()) {
            if (!ObjectUtil.hasProperty(this, child.getName())) {
                throw new ParsedNodeException("Unexpected node: "+child.getName());
            }
        }

        name = parsedNode.getChildValue(null, "name", String.class);
        computed = parsedNode.getChildValue(null, "computed", Boolean.class);
        type = parsedNode.getChildValue(null, "type", String.class);
        encoding = parsedNode.getChildValue(null, "encoding", String.class);
        autoIncrement = parsedNode.getChildValue(null, "autoIncrement", Boolean.class);
        startWith = parsedNode.getChildValue(null, "startWith", BigInteger.class);
        incrementBy = parsedNode.getChildValue(null, "incrementBy", BigInteger.class);
        remarks = parsedNode.getChildValue(null, "remarks", String.class);
        descending = parsedNode.getChildValue(null, "descending", Boolean.class);

        value = parsedNode.getChildValue(null, "value", String.class);
        if (value == null) {
            value = StringUtil.trimToNull((String) parsedNode.getValue());
        }

        setValueNumeric(parsedNode.getChildValue(null, "valueNumeric", String.class));

        try {
            valueDate = parsedNode.getChildValue(null, "valueDate", Date.class);
        } catch (ParsedNodeException e) {
            valueComputed = new DatabaseFunction(parsedNode.getChildValue(null, "valueDate", String.class));
        }
        valueBoolean = parsedNode.getChildValue(null, "valueBoolean", Boolean.class);
        valueBlobFile = parsedNode.getChildValue(null, "valueBlobFile", String.class);
        valueClobFile = parsedNode.getChildValue(null, "valueClobFile", String.class);
        String valueComputedString = parsedNode.getChildValue(null, "valueComputed", String.class);
        if (valueComputedString != null) {
            valueComputed = new DatabaseFunction(valueComputedString);
        }
        String valueSequenceNextString = parsedNode.getChildValue(null, "valueSequenceNext", String.class);
        if (valueSequenceNextString != null) {
            valueSequenceNext = new SequenceNextValueFunction(valueSequenceNextString);
        }
        String valueSequenceCurrentString = parsedNode.getChildValue(null, "valueSequenceCurrent", String.class);
        if (valueSequenceCurrentString != null) {
            valueSequenceCurrent = new SequenceCurrentValueFunction(valueSequenceCurrentString);
        }

        defaultValueConstraintName = parsedNode.getChildValue(null, "defaultValueConstraintName", String.class);

        defaultValue = parsedNode.getChildValue(null, "defaultValue", String.class);

        setDefaultValueNumeric(parsedNode.getChildValue(null, "defaultValueNumeric", String.class));

        try {
            defaultValueDate = parsedNode.getChildValue(null, "defaultValueDate", Date.class);
        } catch (ParsedNodeException e) {
            defaultValueComputed = new DatabaseFunction(parsedNode.getChildValue(null, "defaultValueDate", String.class));
        }
        defaultValueBoolean = parsedNode.getChildValue(null, "defaultValueBoolean", Boolean.class);
        String defaultValueComputedString = parsedNode.getChildValue(null, "defaultValueComputed", String.class);
        if (defaultValueComputedString != null) {
            defaultValueComputed = new DatabaseFunction(defaultValueComputedString);
        }
        String defaultValueSequenceNextString = parsedNode.getChildValue(null, "defaultValueSequenceNext", String.class);
        if (defaultValueSequenceNextString != null) {
            defaultValueSequenceNext = new SequenceNextValueFunction(defaultValueSequenceNextString);
        }

        loadConstraints(parsedNode.getChild(null, "constraints"));
    }

    protected void loadConstraints(ParsedNode constraintsNode) throws ParsedNodeException {
        if (constraintsNode == null) {
            return;
        }

        ConstraintsConfig constraints = new ConstraintsConfig();
        constraints.setNullable(constraintsNode.getChildValue(null, "nullable", Boolean.class));
        constraints.setNotNullConstraintName(constraintsNode.getChildValue(null, "notNullConstraintName", String.class));
        constraints.setPrimaryKey(constraintsNode.getChildValue(null, "primaryKey", Boolean.class));
        constraints.setPrimaryKeyName(constraintsNode.getChildValue(null, "primaryKeyName", String.class));
        constraints.setPrimaryKeyTablespace(constraintsNode.getChildValue(null, "primaryKeyTablespace", String.class));
        constraints.setReferences(constraintsNode.getChildValue(null, "references", String.class));
        constraints.setReferencedTableCatalogName(constraintsNode.getChildValue(null, "referencedTableCatalogName", String.class));
        constraints.setReferencedTableSchemaName(constraintsNode.getChildValue(null, "referencedTableSchemaName", String.class));
        constraints.setReferencedTableName(constraintsNode.getChildValue(null, "referencedTableName", String.class));
        constraints.setReferencedColumnNames(constraintsNode.getChildValue(null, "referencedColumnNames", String.class));
        constraints.setUnique(constraintsNode.getChildValue(null, "unique", Boolean.class));
        constraints.setUniqueConstraintName(constraintsNode.getChildValue(null, "uniqueConstraintName", String.class));
        constraints.setCheckConstraint(constraintsNode.getChildValue(null, "checkConstraint", String.class));
        constraints.setDeleteCascade(constraintsNode.getChildValue(null, "deleteCascade", Boolean.class));
        constraints.setForeignKeyName(constraintsNode.getChildValue(null, "foreignKeyName", String.class));
        constraints.setInitiallyDeferred(constraintsNode.getChildValue(null, "initiallyDeferred", Boolean.class));
        constraints.setDeferrable(constraintsNode.getChildValue(null, "deferrable", Boolean.class));
        constraints.setShouldValidate(constraintsNode.getChildValue(null, "validate", Boolean.class));
        setConstraints(constraints);

    }

    public static class ValueNumeric extends Number {
        private static final long serialVersionUID = 1381154777956917462L;

        private final Number delegate;
        private final String value;

        private ValueNumeric(final String value, final Number numeric) {
            this.delegate = numeric;
            this.value = value;
        }

        private static ValueNumeric of(Locale locale, String value) throws ParseException {
            final Number parsedNumber = NumberFormat.getInstance(locale)
                    .parse(value);
            return new ValueNumeric(value, parsedNumber);
        }

        @Override
        public double doubleValue() {
            return delegate.doubleValue();
        }

        @Override
        public float floatValue() {
            return delegate.floatValue();
        }

        @Override
        public int intValue() {
            return delegate.intValue();
        }

        @Override
        public long longValue() {
            return delegate.longValue();
        }

        @Override
        public String toString() {
            return value;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }

            if (!(obj instanceof Number)) {
                return false;
            }
            return obj.toString().equals(this.toString());
        }

        @Override
        public int hashCode() {
            return this.toString().hashCode();
        }

        public Number getDelegate() {
            return delegate;
        }
    }

    @Override
    public Object getSerializableFieldValue(String field) {
        Object o = ReflectionSerializer.getInstance().getValue(this, field);
        if (field.equals("valueDate") || field.equals("defaultValueDate")) {
            return new ISODateFormat().format((Date)o);
        } else {
            return o;
        }
    }
}


package liquibase.change;

import liquibase.Scope;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.logging.LogService;
import liquibase.logging.Logger;
import liquibase.plugin.AbstractPluginFactory;
import liquibase.plugin.Plugin;
import liquibase.servicelocator.ServiceLocator;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Factory class for constructing the correct liquibase.change.Change implementation based on a command name.
 * For XML-based changelogs, the tag name is the command name.
 * Change implementations are looked up via the {@link ServiceLocator}.
 *
 * @see liquibase.change.Change
 */
public class ChangeFactory extends AbstractPluginFactory<Change>{

    private Map<Class<? extends Change>, ChangeMetaData> metaDataByClass = new ConcurrentHashMap<>();

    private ChangeFactory() {

    }

    @Override
    protected Class<Change> getPluginClass() {
        return Change.class;
    }

    @Override
    protected int getPriority(Change obj, Object... args) {
        String commandName = (String) args[0];
        ChangeMetaData changeMetaData = getChangeMetaData(obj);
        if (commandName.equals(changeMetaData.getName())) {
            return changeMetaData.getPriority();
        } else {
            return Plugin.PRIORITY_NOT_APPLICABLE;
        }
    }

    public ChangeMetaData getChangeMetaData(String change) {
        Change changeObj = create(change);
        if (changeObj == null) {
            return null;
        }
        return getChangeMetaData(changeObj);
    }

    public ChangeMetaData getChangeMetaData(Change change) {
        if (!metaDataByClass.containsKey(change.getClass())) {
            metaDataByClass.put(change.getClass(), change.createChangeMetaData());
        }
        return metaDataByClass.get(change.getClass());
    }

    /**
     * Unregister all instances of a given Change name. Normally used for testing, but can be called manually if needed.
     */
    public void unregister(String name) {
        for (Change change : new ArrayList<>(findAllInstances())) {
            if (getChangeMetaData(change).getName().equals(name)) {
                this.removeInstance(change);
            }
        }
    }

    /**
     * Returns all defined changes in the registry. Returned set is not modifiable.
     */
    public SortedSet<String> getDefinedChanges() {
        SortedSet<String> names = new TreeSet<>();
        for (Change change : findAllInstances()) {
            names.add(getChangeMetaData(change).getName());
        }
        return Collections.unmodifiableSortedSet(names);
    }

    /**
     * Create a new Change implementation for the given change name. The class of the constructed object will be the Change implementation with the highest priority.
     * Each call to create will return a new instance of the Change.
     */
    public Change create(String name) {
        Change plugin = getPlugin(name);
        if (plugin == null) {
            return null;
        }
        try {
            return plugin.getClass().getConstructor().newInstance();
        } catch (Exception e) {
            throw new UnexpectedLiquibaseException(e);
        }
    }

    public Map<String, Object> getParameters(Change change) {
        Map<String, Object> returnMap = new HashMap<>();
        ChangeMetaData changeMetaData = getChangeMetaData(change);
        for (ChangeParameterMetaData param : changeMetaData.getParameters().values()) {
            Object currentValue = param.getCurrentValue(change);
            if (currentValue != null) {
                returnMap.put(param.getParameterName(), currentValue);
            }
        }

        return returnMap;
    }
}


package liquibase.util.ui;

import liquibase.database.Database;
import liquibase.exception.DatabaseException;
import liquibase.util.StreamUtil;

import javax.swing.*;

public class SwingUIFacade implements UIFacade {
    /**
     * Displays swing-based dialog about running against a non-localhost database.
     * Returns true if the user selected that they are OK with that.
     */
    @Override
    public boolean promptForNonLocalDatabase(Database database) throws DatabaseException {
        return JOptionPane.showConfirmDialog(null, "You are running a database migration against a non-local database." + StreamUtil.getLineSeparator() +
                "Database URL is: " + database.getConnection().getURL() + StreamUtil.getLineSeparator() +
                "Username is: " + database.getConnection().getConnectionUserName() + StreamUtil.getLineSeparator() + StreamUtil.getLineSeparator() +
                "Are you sure you want to do this?",
                "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.NO_OPTION;
    }

}