package org.nutz.dao;

import org.nutz.castor.Castors;
import org.nutz.dao.impl.sql.NutSql;
import org.nutz.dao.impl.sql.ValueEscaper;
import org.nutz.dao.impl.sql.callback.*;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.sql.SqlCallback;
import org.nutz.lang.Mirror;
import org.nutz.lang.born.Borning;

/**
 * 提供了 Sql 相关的帮助函数
 *
 * @author zozoh(zozohtnt@gmail.com)
 */
public abstract class Sqls {

    private static final ValueEscaper ES_FLD_VAL = new ValueEscaper();
    private static final ValueEscaper ES_SQL_FLD = new ValueEscaper();
    private static final ValueEscaper ES_CND_VAL = new ValueEscaper();

    private static Borning<? extends Sql> sqlBorning;

    static {
        ES_FLD_VAL.add('\'', "''").add('\\', "\\\\").ready();
        ES_SQL_FLD.add('\'', "''").add('\\', "\\\\").add('$', "$$").add('@', "@@").ready();
        ES_CND_VAL.add('\'', "''").add('\\', "\\\\").add('_', "\\_").add('%', "\\%").ready();

        setSqlBorning(NutSql.class);
    }

    /**
     * 改变 Sql 接口的实现类，如果你调用了这个方法，以后你再调用本类其他帮助函数创建的 SQL 就是你提供的这个实现类
     *

     * 默认的，将用 org.nutz.dao.sql.impl.sql.NutSql 作为实现类
     *

     * 你给出的 Sql 实现类必须有一个可访问的构造函数，接受一个字符串型参数
     *
     * @param type
     *            你的 Sql 接口实现类
     */
    public static <T extends Sql> void setSqlBorning(Class<T> type) {
        sqlBorning = Mirror.me(type).getBorningByArgTypes(String.class);
    }

    /**
     * 创建了一个 Sql 对象。
     *

     * 传入的 Sql 语句支持变量和参数占位符：
     *

     *
变量： 格式为 $XXXX，在执行前，会被预先替换
     *
参数： 格式为@XXXX，在执行前，会替换为 '?'，用以构建 PreparedStatement
     *

     *
     * @param sql
     *            Sql 语句
     * @return Sql 对象
     *
     * @see org.nutz.dao.sql.Sql
     */
    public static Sql create(String sql) {
        return sqlBorning.born(sql);
    }

    /**
     * 创建了一个 Sql 对象。
     *

     * 传入的 Sql 语句支持变量和参数占位符：
     *

     *
变量： 格式为 $XXXX，在执行前，会被预先替换
     *
参数： 格式为@XXXX，在执行前，会替换为 '?'，用以构建 PreparedStatement
     *

     *
     * @param fmt
     *            格式字符，格式参看 String.format 函数
     * @param args
     *            格式字符串的参数
     * @return Sql 对象
     */
    public static Sql createf(String fmt, Object... args) {
        return create(String.format(fmt, args));
    }

    /**
     * 创建一个获取单个实体对象的 Sql。
     *

     * 这个函数除了执行 create(String)外，还会为这个 Sql 语句设置回调，用来获取实体对象。
     *

     * 注意：返回的 Sql 对象在执行前，一定要通过 setEntity 设置
     * 一个有效的实体，否则，会抛出异常。
     *
     * @param sql
     *            Sql 语句
     * @return Sql 对象
     *
     * @see org.nutz.dao.sql.Sql
     * @see org.nutz.dao.entity.Entity
     */
    public static Sql fetchEntity(String sql) {
        return create(sql).setCallback(callback.entity());
    }

    /**
     * 创建一个获取单个 Record 对象的 Sql。
     *

     * 这个函数除了执行 create(String)外，还会为这个 Sql 语句设置回调，用来获取实体对象。
     *
     * @param sql
     *            Sql 语句
     * @return Sql 对象
     *
     * @see org.nutz.dao.sql.Sql
     * @see org.nutz.dao.entity.Entity
     */
    public static Sql fetchRecord(String sql) {
        return create(sql).setCallback(callback.record());
    }

    /**
     * 创建一个获取整数的 Sql。
     *

     * 这个函数除了执行 create(String)外，还会为这个 Sql 语句设置回调，用来获取整数值。
     *

     * 注意：你的 Sql 语句返回的 ResultSet 的第一列必须是数字
     *
     * @param sql
     *            Sql 语句
     * @return Sql 对象
     *
     * @see org.nutz.dao.sql.Sql
     */
    public static Sql fetchInt(String sql) {
        return create(sql).setCallback(callback.integer());
    }

    /**
     * @see #fetchInt(String)
     */
    public static Sql fetchLong(String sql) {
        return create(sql).setCallback(callback.longValue());
    }

    /**
     * @see #fetchInt(String)
     */
    public static Sql fetchFloat(String sql) {
        return create(sql).setCallback(callback.floatValue());
    }

    /**
     * @see #fetchInt(String)
     */
    public static Sql fetchDouble(String sql) {
        return create(sql).setCallback(callback.doubleValue());
    }

    /**
     * @see #fetchInt(String)
     */
    public static Sql fetchTimestamp(String sql) {
        return create(sql).setCallback(callback.timestamp());
    }

    /**
     * 创建一个获取字符串的 Sql。
     *

     * 这个函数除了执行 create(String)外，还会为这个 Sql 语句设置回调，用来获取字符串。
     *

     * 注意：你的 Sql 语句返回的 ResultSet 的第一列必须是字符串
     *
     * @param sql
     *            Sql 语句
     * @return Sql 对象
     *
     * @see org.nutz.dao.sql.Sql
     */
    public static Sql fetchString(String sql) {
        return create(sql).setCallback(callback.str());
    }

    public static Sql queryString(String sql) {
        return create(sql).setCallback(callback.strs());
    }

    /**
     * 创建一个获取一组实体对象的 Sql。
     *

     * 这个函数除了执行 create(String)外，还会为这个 Sql 语句设置回调，用来获取一组实体对象。
     *

     * 注意：返回的 Sql 对象在执行前，一定要通过 setEntity 设置
     * 一个有效的实体，否则，会抛出异常。
     *
     * @param sql
     *            Sql 语句
     * @return Sql 对象
     *
     * @see org.nutz.dao.sql.Sql
     * @see org.nutz.dao.entity.Entity
     */
    public static Sql queryEntity(String sql) {
        return create(sql).setCallback(callback.entities());
    }

    /**
     * 创建一个获取一组 Record 实体对象的 Sql。
     *

     * 这个函数除了执行 create(String)外，还会为这个 Sql 语句设置回调，用来获取一组实体对象。
     *
     * @param sql
     *            Sql 语句
     * @return Sql 对象
     */
    public static Sql queryRecord(String sql) {
        return create(sql).setCallback(callback.records());
    }

    /**
     * 一些内置的回调对象
     */
    public static CallbackFactory callback = new CallbackFactory();

    public static class CallbackFactory {
        /**
         * @return 从 ResultSet获取一个对象的回调对象
         */
        public SqlCallback entity() {
            return entity(null);
        }

        public SqlCallback entity(String prefix) {
            return new FetchEntityCallback(prefix);
        }

        /**
         * @return 从 ResultSet 获取一个 Record 的回调对象
         */
        public SqlCallback record() {
            return new FetchRecordCallback();
        }

        /**
         * @return 从 ResultSet 获取一个整数的回调对象
         */
        public SqlCallback integer() {
            return new FetchIntegerCallback();
        }

        /**
         * @return 从 ResultSet 获取一个长整型数的回调对象
         */
        public SqlCallback longValue() {
            return new FetchLongCallback();
        }

        /**
         * @return 从 ResultSet 获取一个浮点数的回调对象
         */
        public SqlCallback floatValue() {
            return new FetchFloatCallback();
        }

        /**
         * @return 从 ResultSet 获取一个双精度浮点数的回调对象
         */
        public SqlCallback doubleValue() {
            return new FetchDoubleCallback();
        }

        /**
         * @return 从 ResultSet 获取一个时间戳对象的回调对象
         */
        public SqlCallback timestamp() {
            return new FetchTimestampCallback();
        }

        /**
         * @return 从 ResultSet 获取一个字符串的回调对象
         */
        public SqlCallback str() {
            return new FetchStringCallback();
        }

        /**
         * @return 从 ResultSet 获得一个整数数组的回调对象
         */
        public SqlCallback ints() {
            return new QueryIntCallback();
        }

        /**
         * @return 从 ResultSet 获得一个长整型数组的回调对象
         */
        public SqlCallback longs() {
            return new QueryLongCallback();
        }

        /**
         * @return 从 ResultSet 获得一个字符串数组的回调对象
         */
        public SqlCallback strs() {
            return new QueryStringArrayCallback();
        }

        /**
         * @return 从 ResultSet 获得一个字符串列表的回调对象
         */
        public SqlCallback strList() {
            return new QueryStringCallback();
        }

        /**
         * @return 从 ResultSet获取一组对象的回调对象
         */
        public SqlCallback entities() {
            return entities(null);
        }

        public SqlCallback entities(String prefix) {
            return new QueryEntityCallback(prefix);
        }

        /**
         * @return 从 ResultSet 获取一组 Record 的回调对象
         */
        public SqlCallback records() {
            return new QueryRecordCallback();
        }

        public SqlCallback bool() {
            return new FetchBooleanCallback();
        }

        public SqlCallback bools() {
            return new QueryBooleanCallback();
        }

        /**
         * 与record()类似,但区分大小写
         */
        public SqlCallback map() {
            return FetchMapCallback.me;
        }
        /**
         * 与records()类似,但区分大小写
         * @return List<Map>回调
         */
        public SqlCallback maps() {
        	return QueryMapCallback.me;
        }

        /**
         * @return 从 ResultSet 获得一个blob的回调对象
         */
        public SqlCallback blob() {
            return new FetchBlobCallback();
        }
    }

    /**
     * 格式化值，根据值的类型，生成 SQL 字段值的部分，它会考虑 SQL 注入
     *
     * @param v
     *            字段值
     * @return 格式化后的 Sql 字段值，可以直接拼装在 SQL 里面
     */
    public static CharSequence formatFieldValue(Object v) {
        if (null == v)
            return "NULL";
        else if (Sqls.isNotNeedQuote(v.getClass()))
            return Sqls.escapeFieldValue(v.toString());
        else
            return new StringBuilder("'").append(Sqls.escapeFieldValue(Castors.me().castToString(v)))
                                         .append('\'');
    }

    /**
     * 格式化值，根据值的类型，生成 SQL 字段值的部分，它会考虑 SQL 注入，以及 SQL 的 '$' 和 '@' 转义
     *
     * @param v
     *            字段值
     * @return 格式化后的 Sql 字段值，可以直接拼装在 SQL 里面
     */
    public static CharSequence formatSqlFieldValue(Object v) {
        if (null == v)
            return "NULL";
        else if (Sqls.isNotNeedQuote(v.getClass()))
            return Sqls.escapeSqlFieldValue(v.toString());
        else
            return new StringBuilder("'").append(Sqls.escapeSqlFieldValue(v.toString()))
                                         .append('\'');
    }

    /**
     * 将 SQL 的字段值进行转意，可以用来防止 SQL 注入攻击
     *
     * @param s
     *            字段值
     * @return 格式化后的 Sql 字段值，可以直接拼装在 SQL 里面
     */
    public static CharSequence escapeFieldValue(CharSequence s) {
        if (null == s)
            return null;
        return ES_FLD_VAL.escape(s);
    }

    /**
     * 将 SQL 的字段值进行转意，可以用来防止 SQL 注入攻击，

     * 同时，它也会将 Sql 的特殊标记 '$' 和 '@' 进行转译
     *
     * @param s
     *            字段值
     * @return 格式化后的 Sql 字段值，可以直接拼装在 SQL 里面
     */
    public static CharSequence escapeSqlFieldValue(CharSequence s) {
        if (null == s)
            return null;
        return ES_SQL_FLD.escape(s);
    }

    /**
     * 将 SQL 的 WHERE 条件值进行转意，可以用来防止 SQL 注入攻击
     *
     * @param s
     *            字段值
     * @return 格式化后的 Sql 字段值，可以直接拼装在 SQL 里面
     */
    public static CharSequence escapteConditionValue(CharSequence s) {
        if (null == s)
            return null;
        return ES_CND_VAL.escape(s);
    }

    /**
     * 判断一个值，在 SQL 中是否需要单引号
     *
     * @param type
     *            类型
     * @return 是否需要加上单引号
     */
    public static boolean isNotNeedQuote(Class<?> type) {
        Mirror<?> me = Mirror.me(type);
        return me.isBoolean() || me.isPrimitiveNumber();
    }

}


/***
 * ASM: a very small and fast Java bytecode manipulation framework
 * Copyright (c) 2000-2011 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.nutz.repo.org.objectweb.asm;

/**
 * A dynamically extensible vector of bytes. This class is roughly equivalent to
 * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient.
 *
 * @author Eric Bruneton
 */
public class ByteVector {

    /**
     * The content of this vector.
     */
    byte[] data;

    /**
     * Actual number of bytes in this vector.
     */
    int length;

    /**
     * Constructs a new {@link ByteVector ByteVector} with a default initial
     * size.
     */
    public ByteVector() {
        data = new byte[64];
    }

    /**
     * Constructs a new {@link ByteVector ByteVector} with the given initial
     * size.
     *
     * @param initialSize
     *            the initial size of the byte vector to be constructed.
     */
    public ByteVector(final int initialSize) {
        data = new byte[initialSize];
    }

    /**
     * Puts a byte into this byte vector. The byte vector is automatically
     * enlarged if necessary.
     *
     * @param b
     *            a byte.
     * @return this byte vector.
     */
    public ByteVector putByte(final int b) {
        int length = this.length;
        if (length + 1 > data.length) {
            enlarge(1);
        }
        data[length++] = (byte) b;
        this.length = length;
        return this;
    }

    /**
     * Puts two bytes into this byte vector. The byte vector is automatically
     * enlarged if necessary.
     *
     * @param b1
     *            a byte.
     * @param b2
     *            another byte.
     * @return this byte vector.
     */
    ByteVector put11(final int b1, final int b2) {
        int length = this.length;
        if (length + 2 > data.length) {
            enlarge(2);
        }
        byte[] data = this.data;
        data[length++] = (byte) b1;
        data[length++] = (byte) b2;
        this.length = length;
        return this;
    }

    /**
     * Puts a short into this byte vector. The byte vector is automatically
     * enlarged if necessary.
     *
     * @param s
     *            a short.
     * @return this byte vector.
     */
    public ByteVector putShort(final int s) {
        int length = this.length;
        if (length + 2 > data.length) {
            enlarge(2);
        }
        byte[] data = this.data;
        data[length++] = (byte) (s >>> 8);
        data[length++] = (byte) s;
        this.length = length;
        return this;
    }

    /**
     * Puts a byte and a short into this byte vector. The byte vector is
     * automatically enlarged if necessary.
     *
     * @param b
     *            a byte.
     * @param s
     *            a short.
     * @return this byte vector.
     */
    ByteVector put12(final int b, final int s) {
        int length = this.length;
        if (length + 3 > data.length) {
            enlarge(3);
        }
        byte[] data = this.data;
        data[length++] = (byte) b;
        data[length++] = (byte) (s >>> 8);
        data[length++] = (byte) s;
        this.length = length;
        return this;
    }

    /**
     * Puts an int into this byte vector. The byte vector is automatically
     * enlarged if necessary.
     *
     * @param i
     *            an int.
     * @return this byte vector.
     */
    public ByteVector putInt(final int i) {
        int length = this.length;
        if (length + 4 > data.length) {
            enlarge(4);
        }
        byte[] data = this.data;
        data[length++] = (byte) (i >>> 24);
        data[length++] = (byte) (i >>> 16);
        data[length++] = (byte) (i >>> 8);
        data[length++] = (byte) i;
        this.length = length;
        return this;
    }

    /**
     * Puts a long into this byte vector. The byte vector is automatically
     * enlarged if necessary.
     *
     * @param l
     *            a long.
     * @return this byte vector.
     */
    public ByteVector putLong(final long l) {
        int length = this.length;
        if (length + 8 > data.length) {
            enlarge(8);
        }
        byte[] data = this.data;
        int i = (int) (l >>> 32);
        data[length++] = (byte) (i >>> 24);
        data[length++] = (byte) (i >>> 16);
        data[length++] = (byte) (i >>> 8);
        data[length++] = (byte) i;
        i = (int) l;
        data[length++] = (byte) (i >>> 24);
        data[length++] = (byte) (i >>> 16);
        data[length++] = (byte) (i >>> 8);
        data[length++] = (byte) i;
        this.length = length;
        return this;
    }

    /**
     * Puts an UTF8 string into this byte vector. The byte vector is
     * automatically enlarged if necessary.
     *
     * @param s
     *            a String whose UTF8 encoded length must be less than 65536.
     * @return this byte vector.
     */
    public ByteVector putUTF8(final String s) {
        int charLength = s.length();
        if (charLength > 65535) {
            throw new IllegalArgumentException();
        }
        int len = length;
        if (len + 2 + charLength > data.length) {
            enlarge(2 + charLength);
        }
        byte[] data = this.data;
        // optimistic algorithm: instead of computing the byte length and then
        // serializing the string (which requires two loops), we assume the byte
        // length is equal to char length (which is the most frequent case), and
        // we start serializing the string right away. During the serialization,
        // if we find that this assumption is wrong, we continue with the
        // general method.
        data[len++] = (byte) (charLength >>> 8);
        data[len++] = (byte) charLength;
        for (int i = 0; i < charLength; ++i) {
            char c = s.charAt(i);
            if (c >= '\001' && c <= '\177') {
                data[len++] = (byte) c;
            } else {
                length = len;
                return encodeUTF8(s, i, 65535);
            }
        }
        length = len;
        return this;
    }

    /**
     * Puts an UTF8 string into this byte vector. The byte vector is
     * automatically enlarged if necessary. The string length is encoded in two
     * bytes before the encoded characters, if there is space for that (i.e. if
     * this.length - i - 2 >= 0).
     *
     * @param s
     *            the String to encode.
     * @param i
     *            the index of the first character to encode. The previous
     *            characters are supposed to have already been encoded, using
     *            only one byte per character.
     * @param maxByteLength
     *            the maximum byte length of the encoded string, including the
     *            already encoded characters.
     * @return this byte vector.
     */
    ByteVector encodeUTF8(final String s, int i, int maxByteLength) {
        int charLength = s.length();
        int byteLength = i;
        char c;
        for (int j = i; j < charLength; ++j) {
            c = s.charAt(j);
            if (c >= '\001' && c <= '\177') {
                byteLength++;
            } else if (c > '\u07FF') {
                byteLength += 3;
            } else {
                byteLength += 2;
            }
        }
        if (byteLength > maxByteLength) {
            throw new IllegalArgumentException();
        }
        int start = length - i - 2;
        if (start >= 0) {
          data[start] = (byte) (byteLength >>> 8);
          data[start + 1] = (byte) byteLength;
        }
        if (length + byteLength - i > data.length) {
            enlarge(byteLength - i);
        }
        int len = length;
        for (int j = i; j < charLength; ++j) {
            c = s.charAt(j);
            if (c >= '\001' && c <= '\177') {
                data[len++] = (byte) c;
            } else if (c > '\u07FF') {
                data[len++] = (byte) (0xE0 | c >> 12 & 0xF);
                data[len++] = (byte) (0x80 | c >> 6 & 0x3F);
                data[len++] = (byte) (0x80 | c & 0x3F);
            } else {
                data[len++] = (byte) (0xC0 | c >> 6 & 0x1F);
                data[len++] = (byte) (0x80 | c & 0x3F);
            }
        }
        length = len;
        return this;
    }

    /**
     * Puts an array of bytes into this byte vector. The byte vector is
     * automatically enlarged if necessary.
     *
     * @param b
     *            an array of bytes. May be <tt>null</tt> to put <tt>len</tt>
     *            null bytes into this byte vector.
     * @param off
     *            index of the fist byte of b that must be copied.
     * @param len
     *            number of bytes of b that must be copied.
     * @return this byte vector.
     */
    public ByteVector putByteArray(final byte[] b, final int off, final int len) {
        if (length + len > data.length) {
            enlarge(len);
        }
        if (b != null) {
            System.arraycopy(b, off, data, length, len);
        }
        length += len;
        return this;
    }

    /**
     * Enlarge this byte vector so that it can receive n more bytes.
     *
     * @param size
     *            number of additional bytes that this byte vector should be
     *            able to receive.
     */
    private void enlarge(final int size) {
        int length1 = 2 * data.length;
        int length2 = length + size;
        byte[] newData = new byte[length1 > length2 ? length1 : length2];
        System.arraycopy(data, 0, newData, 0, length);
        data = newData;
    }
}


package org.nutz.trans;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import javax.sql.DataSource;

import org.nutz.lang.ComboException;

/**
 * 默认的事务实现上下文类,用户通常不会直接使用到这个类. 这个类会关联同一事务内多个数据源的连接
 * @author wendal(wendal1985@gmail.com)
 *
 */
public class NutTransaction extends Transaction {

    private static AtomicLong TransIdMaker = new AtomicLong();

    private List<ConnInfo> list;

    private long id;

    private static class ConnInfo {
        ConnInfo(DataSource ds, Connection conn, int level, boolean restoreAutoCommit) throws SQLException {
            this.ds = ds;
            this.conn = conn;
            this.oldLevel = conn.getTransactionIsolation();
            if (this.oldLevel != level) {
                conn.setTransactionIsolation(level);
                this.restoreIsoLevel = true;
            }
            this.restoreAutoCommit = restoreAutoCommit;
        }

        DataSource ds;
        Connection conn;
        int oldLevel;
        boolean restoreIsoLevel;
        boolean restoreAutoCommit;
    }

    /**
     * 新建上下文并初始化自身的层次数据
     */
    public NutTransaction() {
        list = new ArrayList<ConnInfo>();
        id = TransIdMaker.getAndIncrement();
    }

    /**
     * 提交事务
     */
    protected void commit() {
        ComboException ce = new ComboException();
        for (ConnInfo cInfo : list) {
            try {
                // 提交事务
                cInfo.conn.commit();
                // 恢复旧的事务级别
                if (cInfo.conn.getTransactionIsolation() != cInfo.oldLevel)
                    cInfo.conn.setTransactionIsolation(cInfo.oldLevel);
            }
            catch (SQLException e) {
                ce.add(e);
            }
        }
        // 如果有一个数据源提交时发生异常，抛出
        if (null != ce.getCause()) {
            throw ce;
        }
    }

    /**
     * 从数据源获取连接
     */
    @Override
    public Connection getConnection(DataSource dataSource) throws SQLException {
        for (ConnInfo p : list)
            if (p.ds == dataSource)
                return p.conn;
        Connection conn = dataSource.getConnection();
        // System.out.printf("=> %s\n", conn.toString());
        boolean restoreAutoCommit = false;
        if (conn.getAutoCommit()) {
            conn.setAutoCommit(false);
            restoreAutoCommit = true;
        }
        // Store conn, it will set the trans level
        list.add(new ConnInfo(dataSource, conn, getLevel(), restoreAutoCommit));
        return conn;
    }

    /**
     * 层次id
     */
    public long getId() {
        return id;
    }

    /**
     * 关闭事务,清理现场
     */
    @Override
    public void close() {
        ComboException ce = new ComboException();
        for (ConnInfo cInfo : list) {
            try {
                // 试图恢复旧的事务级别
                if (!cInfo.conn.isClosed()) {
                    if (cInfo.restoreIsoLevel)
                        cInfo.conn.setTransactionIsolation(cInfo.oldLevel);
                    if (cInfo.restoreAutoCommit)
                        cInfo.conn.setAutoCommit(true);
                }
            }
            catch (Throwable e) {}
            finally {
                try {
                    cInfo.conn.close();
                }
                catch (Exception e) {
                    ce.add(e);
                }
            }
        }
        // 清除数据源记录
        list.clear();
    }

    /**
     * 执行回滚操作
     */
    @Override
    protected void rollback() {
        for (ConnInfo cInfo : list) {
            try {
                cInfo.conn.rollback();
            }
            catch (Throwable e) {}
        }
    }

}


package org.nutz.runner;

import java.util.Date;

import org.nutz.lang.Times;
import org.nutz.log.Log;
import org.nutz.log.Logs;

/**
 * 封装Runnable的带lock的启动器
 *
 * @author zozoh(zozohtnt@gmail.com)
 * @author pw
 * @author wendal
 */
public abstract class NutRunner implements Runnable {

    protected Log log;

    /**
     * 所属的关联线程
     */
    protected Thread myThread;

    /**
     * 本运行器名称
     */
    protected String rnm;

    /**
     * 线程锁
     */
    protected NutLock lock;

    /**
     * 累积启动次数
     */
    protected int count;

    /**
     * 本次睡眠时间
     */
    protected long interval;

    /**
     * 报错以后睡眠时间
     */
    protected int sleepAfterError;

    /**
     * 启动于
     */
    protected Date upAt;

    /**
     * 睡眠于，如果本值不为 null，表示本线程正在睡眠，否则为运行中
     */
    protected Date downAt;

    protected boolean debug = true;

    /**
     * 新建一个启动器
     *
     * @param rname
     *            本启动器的名称
     */
    public NutRunner(String rname) {
        this.rnm = rname;
        this.count = 0;
        this.sleepAfterError = 30;
        this.lock = new NutLock();
    }

    /**
     * 设置报错以后睡眠时间（单位秒）
     *
     * @param sec
     *            秒
     * @return 冷却时间
     */
    public NutRunner setSleepAfterError(int sec) {
        this.sleepAfterError = sec;
        return this;
    }

    /**
     * 主逻辑,用户代码不应该覆盖.
     */
    public void run() {
        if (log == null) {
            log = Logs.get().setTag(rnm);
        }
        myThread = Thread.currentThread();

        beforeStart(this);
        doIt();
        afterStop(this);
    }

    /**
     * 具体的业务实现,返回一个sleep数
     *
     * @return 本次运行后还需要等待多少个毫秒
     */
    public abstract long exec() throws Exception;

    /**
     * 注册本对象到线程管理器,已废弃
     *
     * @param me
     *            本对象
     */
    @Deprecated
    public void reg(NutRunner me) {}

    /**
     * 从线程管理器反注册,已废弃
     *
     * @param me
     *            本对象
     */
    @Deprecated
    public void unreg(NutRunner me) {}

    /**
     * 开始之前,一般做一些准备工作,比如资源初始化等
     *
     * @param me
     *            runner本身
     */
    public void beforeStart(NutRunner me) {
        reg(me);
    }

    /**
     * 停止之后,一般是做一些资源回收
     *
     * @param me
     *            runner本身
     */
    public void afterStop(NutRunner me) {
        unreg(me);
    }

    /**
     * 做一些需要定期执行的操作
     */
    protected void doIt() {
        while (!lock.isStop()) {
            synchronized (lock) {
                try {
                    // 修改一下本线程的时间
                    upAt = Times.now();
                    downAt = null;
                    if (debug && log.isDebugEnabled())
                        log.debugf("%s [%d] : up", rnm, ++count);

                    // 执行业务
                    interval = exec();

                    if (interval < 1)
                        interval = 1; // 不能间隔0或者负数,会死线程的

                    // 等待一个周期
                    downAt = Times.now();
                    if (debug && log.isDebugEnabled())
                        log.debugf("%s [%d] : wait %ds(%dms)", rnm, count, interval / 1000, interval);
                    lock.wait(interval);
                }
                catch (InterruptedException e) {
                    log.warn(String.format("%s has been interrupted", rnm), e);
                    break;
                }
                catch (Throwable e) {
                    log.warn(String.format("%s has some error", rnm), e);
                    try {
                        lock.wait(sleepAfterError * 1000);
                    }
                    catch (Throwable e1) {
                        log.warn(String.format("%s has some error again", rnm), e);
                        break;
                    }
                }
            }
        }
    }

    /**
     * 返回格式为 [名称:总启动次数] 最后启动时间:最后休眠时间 - 休眠间隔
     */
    public String toString() {
        return String.format("[%s:%d] %s/%s - %d",
                             rnm,
                             count,
                             upAt == null ? "NONE" : Times.sDT(upAt),
                             downAt == null ? "NONE" : Times.sDT(downAt),
                             interval);
    }

    /**
     * 是否正在等待运行
     *
     * @return true,如果正在等待
     */
    public boolean isWaiting() {
        return null != downAt;
    }

    /**
     * 是否正在执行用户代码
     *
     * @return true,如果正在exec方法内部
     */
    public boolean isRunning() {
        return null == downAt;
    }

    /**
     * 获取执行间隔
     *
     * @return 执行间隔
     */
    public long getInterval() {
        return interval;
    }

    /**
     * 获取最后启动时间
     *
     * @return 最后启动时间
     */
    public Date getUpAt() {
        return upAt;
    }

    /**
     * 获取最后一次等待开始的时间
     *
     * @return 最后一次等待开始的时间
     */
    public Date getDownAt() {
        return downAt;
    }

    /**
     * 获取本启动器的名称
     *
     * @return 本启动器的名称
     */
    public String getName() {
        return rnm;
    }

    /**
     * 获取累计的启动次数
     *
     * @return 总启动次数
     */
    public int getCount() {
        return count;
    }

    /**
     * 获取线程NutLock锁
     *
     * @return 线程NutLock锁
     */
    public NutLock getLock() {
        return lock;
    }

    /**
     * 获取所属线程是否存活
     *
     * @return 所属线程是否存活
     */
    public boolean isAlive() {
        if (myThread != null) {
            return myThread.isAlive();
        }
        return false;
    }

    /**
     * 强行关闭所属线程
     *
     * @param err
     *            传给Thread.stop方法的对象
     */
    @SuppressWarnings("deprecation")
    public void stop(Throwable err) {
        myThread.stop(err);
    }

    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    public boolean isDebug() {
        return debug;
    }
}


package org.nutz.dao.impl.sql;

import java.lang.reflect.Array;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.nutz.castor.Castors;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.entity.Entity;
import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.sql.SqlCallback;
import org.nutz.lang.Lang;

/**
 * 仿照Spring JdbcTemplate实现nutz的SqlTemplate，方便sql的调用
 *
 * @author hzl7652(hzl7652@sina.com)
 */
public class SqlTemplate {

    private Dao dao;

    public SqlTemplate() {}

    public SqlTemplate(Dao dao) {
        setDao(dao);
    }

    public void setDao(Dao dao) {
        this.dao = dao;
    }

    public Dao dao() {
        return this.dao;
    }

    /**
     * 执行一个SQL更新操作（如插入，更新或删除语句）。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param params
     *            参数map,无参数时，可为null
     *
     * @return SQL 语句所影响的行数
     */
    public int update(String sql, Map<String, Object> params) {
        return update(sql, null, params);
    }

    /**
     * 执行一个SQL更新操作（如插入，更新或删除语句）。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param vars
     *            变量map，无参数时，可为null
     * @param params
     *            参数map，无参数时，可为null
     *
     * @return SQL 语句所影响的行数
     */
    public int update(String sql, Map<String, Object> vars, Map<String, Object> params) {
        Sql sqlObj = createSqlObj(sql, params);
        execute(sqlObj, vars, params);
        return sqlObj.getUpdateCount();
    }

    /**
     * 执行SQL批量更新操作（如插入，更新或删除语句）。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param batchValues
     *            批量更新参数集合
     *
     * @return SQL 语句所影响的行数
     */
    public int batchUpdate(String sql, List<Map<String, Object>> batchValues) {
        return batchUpdate(sql, null, batchValues);
    }

    /**
     * 执行SQL批量更新操作（如插入，更新或删除语句）。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param vars
     *            变量map，无参数时，可为null
     * @param batchValues
     *            批量更新参数集合
     *
     * @return SQL 语句所影响的行数
     */
    public int batchUpdate(String sql,
                           Map<String, Object> vars,
                           List<Map<String, Object>> batchValues) {
        Sql sqlObj = null;
        if (batchValues != null && batchValues.size() > 0) {
            sqlObj = createSqlObj(sql, batchValues.get(0));
            for (Map<String, Object> params : batchValues) {
                Map<String, Object> newParams = paramProcess(params);
                sqlObj.params().putAll(newParams);
                sqlObj.addBatch();
            }
            dao.execute(sqlObj);
        } else {
            sqlObj = createSqlObj(sql, null);
            execute(sqlObj, vars, null);
        }

        return sqlObj.getUpdateCount();
    }

    /**
     * 执行一个SQL查询操作，结果为一个int形数值。
     *

     *
     * @param sql
     *            包含变量占位符的SQL
     * @param params
     *            参数map，无参数时，可为null
     *
     * @return int数值，当查询为null时返回0
     */
    public int queryForInt(String sql, Map<String, Object> params) {
        return queryForInt(sql, null, params);
    }

    /**
     * 执行一个SQL查询操作，结果为一个int形数值。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param vars
     *            变量map，无参数时，可为null
     * @param params
     *            参数map，无参数时，可为null
     *
     * @return int数值，当查询为null时返回0
     */
    public int queryForInt(String sql, Map<String, Object> vars, Map<String, Object> params) {
        Sql sqlObj = createSqlObj(sql, params);
        sqlObj.setCallback(Sqls.callback.integer());
        execute(sqlObj, vars, params);
        return sqlObj.getInt();
    }

    /**
     * 执行一个SQL查询操作，结果为一个long形数值。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param params
     *            参数map，无参数时，可为null
     *
     * @return long数值，当查询为null时返回0
     */
    public long queryForLong(String sql, Map<String, Object> params) {
        return queryForLong(sql, null, params);
    }

    /**
     * 执行一个SQL查询操作，结果为一个long形数值。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param vars
     *            变量map，无参数时，可为null
     * @param params
     *            参数map，无参数时，可为null
     *
     * @return long数值，当查询为null时返回0
     */
    public long queryForLong(String sql, Map<String, Object> vars, Map<String, Object> params) {
        Sql sqlObj = createSqlObj(sql, params);
        sqlObj.setCallback(Sqls.callback.longValue());
        execute(sqlObj, vars, params);
        Long result = sqlObj.getObject(Long.class);
        return result == null ? 0 : result;
    }

    /**
     * 执行一个SQL查询操作，结果为给定对象类型的对象，适用于明确SQL查询结果的类型。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param params
     *            参数map 无参数时，可为null
     * @param classOfT
     *            对象类型，SQL查询结果所对应的类型，如Date.class等
     *
     * @return 对象，无查询结果时返回null
     */
    public <T> T queryForObject(String sql, Map<String, Object> params, Class<T> classOfT) {
        return queryForObject(sql, null, params, classOfT);
    }

    /**
     * 执行一个SQL查询操作，结果为给定对象类型的对象，适用于明确SQL查询结果的类型。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param vars
     *            变量map，无参数时，可为null
     * @param params
     *            参数map，无参数时，可为null
     * @param classOfT
     *            对象类型，SQL查询结果所对应的类型，如Date.class等
     *
     * @return 对象，无查询结果时返回null
     */
    public <T> T queryForObject(String sql,
                                Map<String, Object> vars,
                                Map<String, Object> params,
                                Class<T> classOfT) {
        Sql sqlObj = createSqlObj(sql, params);
        sqlObj.setCallback(new SqlCallback() {
            public Object invoke(Connection conn, ResultSet rs, Sql sql) throws SQLException {
                if (null != rs && rs.next())
                    return rs.getObject(1);
                return null;
            }
        });

        execute(sqlObj, vars, params);
        return sqlObj.getObject(classOfT);
    }

    /**
     * 执行一个SQL查询操作，结果为给定实体的对象。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param params
     *            参数map，无参数时，可为null
     * @param entity
     *            实体类型，无参数时，可为null
     *
     * @return 对象，无查询结果时返回null
     */
    public <T> T queryForObject(String sql, Map<String, Object> params, Entity<T> entity) {
        return queryForObject(sql, null, params, entity);
    }

    /**
     * 执行一个SQL查询操作，结果为给定实体的对象。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param vars
     *            变量map，无参数时，可为null
     * @param params
     *            参数map，无参数时，可为null
     * @param entity
     *            实体类型
     *
     * @return 对象，无查询结果时返回null
     */
    public <T> T queryForObject(String sql,
                                Map<String, Object> vars,
                                Map<String, Object> params,
                                Entity<T> entity) {
        Sql sqlObj = createSqlObj(sql, params);
        sqlObj.setCallback(Sqls.callback.entity());
        sqlObj.setEntity(entity);

        execute(sqlObj, vars, params);

        return sqlObj.getObject(entity.getType());
    }

    /**
     * 执行一个SQL查询操作，结果为Record的对象。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param params
     *            参数map，无参数时，可为null
     *
     * @return Record对象，无查询结果时返回null
     */
    public Record queryForRecord(String sql, Map<String, Object> params) {
        return queryForRecord(sql, null, params);
    }

    /**
     * 执行一个SQL查询操作，结果为Record的对象。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param vars
     *            变量map，无参数时，可为null
     * @param params
     *            参数map，无参数时，可为null
     * @return Record对象，无查询结果时返回null
     */
    public Record queryForRecord(String sql, Map<String, Object> vars, Map<String, Object> params) {
        Sql sqlObj = createSqlObj(sql, params);
        sqlObj.setCallback(Sqls.callback.record());

        execute(sqlObj, vars, params);

        return sqlObj.getObject(Record.class);
    }

    /**
     * 执行一个SQL查询操作，结果为一组对象。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param params
     *            参数map，无参数时，可为null
     * @param entity
     *            对象类型，无参数时，可为null
     *
     * @return 对象列表，无查询结果时返回长度为0的List对象
     */
    public <T> List<T> query(String sql, Map<String, Object> params, Entity<T> entity) {
        return query(sql, null, params, entity);
    }

    /**
     * 执行一个SQL查询操作，结果为一组对象。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param params
     *            参数map，无参数时，可为null
     * @param classOfT
     *            对象类类
     *
     * @return 对象列表，无查询结果时返回长度为0的List对象
     */
    public <T> List<T> query(String sql,
                             Map<String, Object> params,
                             Class<T> classOfT) {
        return query(sql, null, params, dao.getEntity(classOfT));
    }

    /**
     * 执行一个SQL查询操作，结果为一组对象。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param vars
     *            变量map，无参数时，可为null
     * @param params
     *            参数map，无参数时，可为null
     * @param entity
     *            对象类型
     *
     * @return 对象列表，无查询结果时返回长度为0的List对象
     */
    public <T> List<T> query(String sql,
                             Map<String, Object> vars,
                             Map<String, Object> params,
                             Entity<T> entity) {
        Sql sqlObj = createSqlObj(sql, params);
        sqlObj.setCallback(Sqls.callback.entities());
        sqlObj.setEntity(entity);

        execute(sqlObj, vars, params);

        return sqlObj.getList(entity.getType());
    }

    /**
     * 执行一个SQL查询操作，结果为一组对象。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param vars
     *            变量map，无参数时，可为null
     * @param params
     *            参数map，无参数时，可为null
     * @param classOfT
     *            对象类型
     *
     * @return 对象列表，无查询结果时返回长度为0的List对象
     */
    public <T> List<T> queryForList(String sql,
                                    Map<String, Object> vars,
                                    Map<String, Object> params,
                                    final Class<T> classOfT) {
        Sql sqlObj = createSqlObj(sql, params);

        sqlObj.setCallback(new SqlCallback() {
            public Object invoke(Connection conn, ResultSet rs, Sql sql) throws SQLException {
                List<T> list = new ArrayList<T>();
                while (rs.next()) {
                    T result = Castors.me().castTo(rs.getObject(1), classOfT);
                    list.add(result);
                }
                return list;
            }
        });

        execute(sqlObj, vars, params);

        return sqlObj.getList(classOfT);
    }

    /**
     * 执行一个SQL查询操作，结果为Record对象列表。
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param vars
     *            变量map，无参数时，可为null
     * @param params
     *            参数map，无参数时，可为null
     *
     * @return Record列表，无查询结果时返回长度为0的List对象
     */
    public List<Record> queryRecords(String sql,
                                     Map<String, Object> vars,
                                     Map<String, Object> params) {
        Sql sqlObj = createSqlObj(sql, params);
        sqlObj.setCallback(Sqls.callback.records());

        execute(sqlObj, vars, params);

        return sqlObj.getList(Record.class);
    }

    /**
     * 设置sql参数并执行sql。
     */
    private void execute(Sql sqlObj, Map<String, Object> vars, Map<String, Object> params) {
        if (vars != null)
            sqlObj.vars().putAll(vars);

        if (params != null) {
            Map<String, Object> newParams = paramProcess(params);
            sqlObj.params().putAll(newParams);
        }

        dao().execute(sqlObj);
    }

    /**
     * 创建Sql对象。
     *

     * 在这里处理Array Collection类型参数，方便SQL IN 表达式的设置
     *
     * @param sql
     *            包含变量占位符的SQL
     * @param params
     *            参数map，无参数时，可为null
     *
     * @return Sql对象
     */
    private Sql createSqlObj(String sql, Map<String, Object> params) {

        if (params == null)
            return Sqls.create(sql);

        String newSql = sqlProcess(sql, params);

        return Sqls.create(newSql);
    }

    /**
     * 将Array Collection类型参数对应的sql占位符进行处理
     *
     * @param originSql
     *            原包含变量占位符的SQL
     * @param params
     *            参数map，无参数时，可为null
     *
     * @return 包含处理IN表达式的sql
     */
    private String sqlProcess(String originSql, Map<String, Object> params) {

        if (params == null || params.size() == 0)
            return originSql;

        String newSql = originSql;
        for (Entry<String, Object> entry : params.entrySet()) {
            String paramName = entry.getKey();
            Object paramObj = entry.getValue();

            if (paramObj.getClass().isArray()) {
                String inSqlExp = inSqlProcess(paramName, paramObj);
                newSql = newSql.replaceAll("@" + paramName, inSqlExp);
            }

            if (paramObj instanceof Collection) {
                Collection<?> collection = (Collection<?>) paramObj;
                Object[] paramVals = Lang.collection2array(collection);
                String inSqlExp = inSqlProcess(paramName, paramVals);
                newSql = newSql.replaceAll("@" + paramName, inSqlExp);
            }

        }

        return newSql;
    }

    /**
     * sql参数处理，在这里处理Array Collection类型参数，方便SQL IN 表达式的设置
     *
     * @param params
     *            参数map，无参数时，可为null
     *
     * @return 包含处理IN表达式的sql
     */
    private Map<String, Object> paramProcess(Map<String, Object> params) {
        if (params == null || params.size() == 0)
            return null;
        Map<String, Object> newParams = new HashMap<String, Object>(params);
        for (Entry<String, Object> entry : params.entrySet()) {
            String paramName = entry.getKey();
            Object paramObj = entry.getValue();

            if (paramObj.getClass().isArray()) {
                inParamProcess(paramName, paramObj, newParams);
                newParams.remove(paramName);
            }

            if (paramObj instanceof Collection) {
                Collection<?> collection = (Collection<?>) paramObj;
                Object[] paramVals = Lang.collection2array(collection);
                inParamProcess(paramName, paramVals, newParams);
                newParams.remove(paramName);
            }

        }

        return newParams;
    }

    private static String inSqlProcess(String paramName, Object paramObj) {
        int len = Array.getLength(paramObj);
        StringBuilder inSqlExp = new StringBuilder();
        for (int i = 0; i < len; i++) {
            inSqlExp.append("@").append(paramName).append(i).append(",");
        }
        inSqlExp.deleteCharAt(inSqlExp.length() - 1);

        return inSqlExp.toString();
    }

    private static void inParamProcess(String paramName, Object paramObj, Map<String, Object> newParams) {
        int len = Array.getLength(paramObj);
        for (int i = 0; i < len; i++) {
            String inParamName = paramName + i;
            newParams.put(inParamName, Array.get(paramObj, i));
        }
    }

}


package liquibase.database.core;

import liquibase.CatalogAndSchema;
import liquibase.Scope;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.executor.ExecutorService;
import liquibase.logging.LogService;
import liquibase.logging.LogType;
import liquibase.statement.SqlStatement;
import liquibase.statement.core.GetViewDefinitionStatement;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.*;
import liquibase.util.JdbcUtils;
import liquibase.util.StringUtil;

import java.math.BigInteger;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Encapsulates MS-SQL database support.
 */
public class MSSQLDatabase extends AbstractJdbcDatabase {
    public static final String PRODUCT_NAME = "Microsoft SQL Server";

    public static final class MSSQL_SERVER_VERSIONS {
        public static final int MSSQL2008 = 10;
        public static final int MSSQL2012 = 11;
        public static final int MSSQL2014 = 12;
        public static final int MSSQL2016 = 13;
        public static final int MSSQL2017 = 14;

        private MSSQL_SERVER_VERSIONS() {
            throw new IllegalStateException("this class is not expected to be instantiated.");
        }
    }

    private HashMap<String, Integer> defaultDataTypeParameters = new HashMap<>();

    protected static final int MSSQL_DEFAULT_TCP_PORT = 1433;

    private static final Pattern CREATE_VIEW_AS_PATTERN =
        Pattern.compile(
            "(?im)^\\s*(CREATE|ALTER)\\s+VIEW\\s+(\\S+)\\s+?AS\\s*",
            Pattern.CASE_INSENSITIVE | Pattern.DOTALL
        );

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

    private Boolean sendsStringParametersAsUnicode;

    // "Magic numbers" are ok here because we populate a lot of self-explaining metadata.
    @SuppressWarnings("squid:S109")
    public MSSQLDatabase() {
        super.setCurrentDateTimeFunction("GETDATE()");

        super.sequenceNextValueFunction = "NEXT VALUE FOR %s";

        systemTablesAndViews.add("syscolumns");
        systemTablesAndViews.add("syscomments");
        systemTablesAndViews.add("sysdepends");
        systemTablesAndViews.add("sysfilegroups");
        systemTablesAndViews.add("sysfiles");
        systemTablesAndViews.add("sysfiles1");
        systemTablesAndViews.add("sysforeignkeys");
        systemTablesAndViews.add("sysfulltextcatalogs");
        systemTablesAndViews.add("sysfulltextnotify");
        systemTablesAndViews.add("sysindexes");
        systemTablesAndViews.add("sysindexkeys");
        systemTablesAndViews.add("sysmembers");
        systemTablesAndViews.add("sysobjects");
        systemTablesAndViews.add("syspermissions");
        systemTablesAndViews.add("sysproperties");
        systemTablesAndViews.add("sysprotects");
        systemTablesAndViews.add("sysreferences");
        systemTablesAndViews.add("systypes");
        systemTablesAndViews.add("sysusers");
        systemTablesAndViews.add("sysdiagrams");
        systemTablesAndViews.add("syssegments");
        systemTablesAndViews.add("sysconstraints");

        // Information obtained from:
        // https://docs.microsoft.com/en-us/sql/t-sql/data-types/precision-scale-and-length-transact-sql
        defaultDataTypeParameters.put("datetime", 3);
        defaultDataTypeParameters.put("datetime2", 7);
        defaultDataTypeParameters.put("datetimeoffset", 7);
        defaultDataTypeParameters.put("time", 7);
        defaultDataTypeParameters.put("decimal", 0);
        defaultDataTypeParameters.put("numeric", 0);
        defaultDataTypeParameters.put("bigint", 0);
        defaultDataTypeParameters.put("int", 0);
        defaultDataTypeParameters.put("smallint", 0);
        defaultDataTypeParameters.put("tinyint", 0);
        defaultDataTypeParameters.put("money", 4);
        defaultDataTypeParameters.put("smallmoney", 0);

        addReservedWords(createReservedWordsCollection());
    }

    @Override
    public Integer getDefaultScaleForNativeDataType(String nativeDataType) {
        return defaultDataTypeParameters.get(nativeDataType.toLowerCase());
    }

    @Override
    public void setDefaultSchemaName(String schemaName) {
        if (schemaName != null && !schemaName.equalsIgnoreCase(getConnectionSchemaName())) {
            throw new RuntimeException(String.format(
                "Cannot use default schema name %s on Microsoft SQL Server because the login " +
                    "schema of the current user (%s) is different and MSSQL does not support " +
                    "setting the default schema per session.", schemaName, getConnectionSchemaName()));
        }
        super.setDefaultSchemaName(schemaName);
    }

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

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

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

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

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

    @Override
    protected Set<String> getSystemTables() {
        return systemTablesAndViews;
    }

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

    @Override
    public boolean supportsSequences() {
        try {
            if (isAzureDb() || this.getDatabaseMajorVersion() >= MSSQL_SERVER_VERSIONS.MSSQL2012) {
                return true;
            }
        } catch (DatabaseException e) {
            throw new UnexpectedLiquibaseException(e);
        }
        return false;
    }

    @Override
    public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
        String databaseProductName = conn.getDatabaseProductName();
        int majorVersion = conn.getDatabaseMajorVersion();
        boolean isRealSqlServerConnection = PRODUCT_NAME.equalsIgnoreCase(databaseProductName)
                || "SQLOLEDB".equalsIgnoreCase(databaseProductName);

        if (isRealSqlServerConnection && (majorVersion < MSSQL_SERVER_VERSIONS.MSSQL2008)) {
            Scope.getCurrentScope().getLog(getClass()).warning(
                LogType.LOG, String.format("Your SQL Server major version (%d) seems to indicate that your " +
                        "software is older than SQL Server 2008. Unfortunately, this is not supported, and this " +
                        "connection cannot be used.",
                 majorVersion));
            return false;
        }
        return isRealSqlServerConnection;
    }

    @Override
    public String getDefaultDriver(String url) {
        if (url.startsWith("jdbc:sqlserver")) {
            return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
        } else if (url.startsWith("jdbc:jtds:sqlserver")) {
            return "net.sourceforge.jtds.jdbc.Driver";
        }
        return null;
    }

    @Override
    protected String getAutoIncrementClause() {
        return "IDENTITY";
    }

    @Override
    protected boolean generateAutoIncrementStartWith(BigInteger startWith) {
        return true;
    }

    @Override
    protected boolean generateAutoIncrementBy(BigInteger incrementBy) {
        return true;
    }

    @Override
    protected String getAutoIncrementStartWithClause() {
        return "%d";
    }

    @Override
    protected String getAutoIncrementByClause() {
        return "%d";
    }

    @Override
    public String getDefaultCatalogName() {
        if (getConnection() == null) {
            return null;
        }
        try {
            return getConnection().getCatalog();
        } catch (DatabaseException e) {
            throw new UnexpectedLiquibaseException(e);
        }
    }

    @Override
    protected SqlStatement getConnectionSchemaNameCallStatement() {
        return new RawSqlStatement("select schema_name()");
    }

    @Override
    public String getConcatSql(String... values) {
        StringBuffer returnString = new StringBuffer();
        for (String value : values) {
            returnString.append(value).append(" + ");
        }

        return returnString.toString().replaceFirst(" \\+ $", "");
    }

    @Override
    public String escapeTableName(String catalogName, String schemaName, String tableName) {
        return escapeObjectName(catalogName, schemaName, tableName, Table.class);
    }

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

    @Override
    public boolean isSystemObject(DatabaseObject example) {
        if ((example.getSchema() == null) || (example.getSchema().getName() == null)) {
            return super.isSystemObject(example);
        }

        if ((example instanceof Table) && "sys".equals(example.getSchema().getName())) {
            return true;
        }
        if ((example instanceof View) && "sys".equals(example.getSchema().getName())) {
            return true;
        }
        return super.isSystemObject(example);
    }

    public String generateDefaultConstraintName(String tableName, String columnName) {
        return "DF_" + tableName + "_" + columnName;
    }

    @Override
    public String escapeObjectName(String objectName, Class<? extends DatabaseObject> objectType) {
        if (objectName == null) {
            return null;
        }

        if (objectName.contains("(")) {
            // probably a function
            return objectName;
        }

        return super.escapeObjectName(objectName, objectType);
    }

    @Override
    public String getDateLiteral(String isoDate) {
        return super.getDateLiteral(isoDate).replace(' ', 'T');
    }

    @Override
    public boolean supportsRestrictForeignKeys() {
        return false;
    }

    @Override
    public boolean supportsDropTableCascadeConstraints() {
        return false;
    }

    @Override
    public boolean supportsCatalogInObjectName(Class<? extends DatabaseObject> type) {
        if (View.class.isAssignableFrom(type)) {
            // Microsoft SQL Server does not allow a catalog name in the CREATE ... VIEW statement:
            // https://docs.microsoft.com/en-gb/sql/t-sql/statements/create-view-transact-sql
            return false;
        } else {
            return Relation.class.isAssignableFrom(type);
        }
    }

    @Override
    public String getViewDefinition(CatalogAndSchema schema, String viewName) throws DatabaseException {
        schema = schema.customize(this);
        List<String> defLines = (List<String>) ExecutorService.getInstance().getExecutor(this)
            .queryForList(
                new GetViewDefinitionStatement(schema.getCatalogName(), schema.getSchemaName(), viewName),
                String.class
            );
        StringBuffer sb = new StringBuffer();
        for (String defLine : defLines) {
            sb.append(defLine);
        }
        String definition = sb.toString();

        String finalDef =definition.replaceAll("\\r\\n", "\n").trim();

        // Keep comment as beginning of statement:
        if (finalDef.startsWith("--") || finalDef.startsWith("/*")) {
            return "FULL_DEFINITION: " + finalDef;
        }

        String selectOnly = CREATE_VIEW_AS_PATTERN.matcher(finalDef).replaceFirst("");
        if (selectOnly.equals(finalDef)) {
            return "FULL_DEFINITION: " + finalDef;
        }

        selectOnly = selectOnly.trim();

        // handle views that end up as '(select XYZ FROM ABC);'
        if (selectOnly.startsWith("(") && (selectOnly.endsWith(")") || selectOnly.endsWith(");"))) {
            selectOnly = selectOnly.replaceFirst("^\\(", "");
            selectOnly = selectOnly.replaceFirst("\\);?$", "");
        }

        return selectOnly;
    }

    @Override
    public String escapeObjectName(String catalogName, String schemaName, String objectName,
                                   Class<? extends DatabaseObject> objectType) {
        if (Index.class.isAssignableFrom(objectType)) {
            return super.escapeObjectName(objectName, objectType);
        }

        if ((catalogName != null) && !catalogName.equalsIgnoreCase(this.getDefaultCatalogName())) {
            return super.escapeObjectName(catalogName, schemaName, objectName, objectType);
        } else {
            String name = this.escapeObjectName(objectName, objectType);
            if (StringUtil.isEmpty(schemaName)) {
                schemaName = this.getDefaultSchemaName();
            }
            if ((!StringUtil.isEmpty(schemaName) && (!schemaName.equals(getConnectionSchemaName())))) {
                name = this.escapeObjectName(schemaName, Schema.class)+"."+name;
            }
            return name;
        }
    }

    @Override
    public String getJdbcSchemaName(CatalogAndSchema schema) {
        String schemaName = super.getJdbcSchemaName(schema);
        if ((schemaName != null) && !isCaseSensitive()) {
            schemaName = schemaName.toLowerCase();
        }
        return schemaName;
    }

    @Override
    public boolean isCaseSensitive() {
        if (caseSensitive == null) {
            try {
                if (getConnection() instanceof JdbcConnection) {
                    String catalog = getConnection().getCatalog();
                    String sql =
                        "SELECT CONVERT([sysname], DATABASEPROPERTYEX(N'" + escapeStringForDatabase(catalog) +
                            "', 'Collation'))";
                    String collation = ExecutorService.getInstance().getExecutor(this)
                        .queryForObject(new RawSqlStatement(sql), String.class);
                    caseSensitive = (collation != null) && !collation.contains("_CI_");
                } else if (getConnection() instanceof OfflineConnection) {
                    caseSensitive = ((OfflineConnection) getConnection()).isCaseSensitive();
                }
            } catch (DatabaseException e) {
                Scope.getCurrentScope().getLog(getClass()).warning(LogType.LOG, "Cannot determine case sensitivity from MSSQL", e);
            }
        }
        return (caseSensitive != null) && caseSensitive;
    }

    @Override
    public int getDataTypeMaxParameters(String dataTypeName) {
        if ("bigint".equalsIgnoreCase(dataTypeName)
                || "bit".equalsIgnoreCase(dataTypeName)
                || "date".equalsIgnoreCase(dataTypeName)
                || "datetime".equalsIgnoreCase(dataTypeName)
                || "geography".equalsIgnoreCase(dataTypeName)
                || "geometry".equalsIgnoreCase(dataTypeName)
                || "hierarchyid".equalsIgnoreCase(dataTypeName)
                || "image".equalsIgnoreCase(dataTypeName)
                || "int".equalsIgnoreCase(dataTypeName)
                || "money".equalsIgnoreCase(dataTypeName)
                || "ntext".equalsIgnoreCase(dataTypeName)
                || "real".equalsIgnoreCase(dataTypeName)
                || "smalldatetime".equalsIgnoreCase(dataTypeName)
                || "smallint".equalsIgnoreCase(dataTypeName)
                || "smallmoney".equalsIgnoreCase(dataTypeName)
                || "text".equalsIgnoreCase(dataTypeName)
                || "timestamp".equalsIgnoreCase(dataTypeName)
                || "tinyint".equalsIgnoreCase(dataTypeName)
                || "rowversion".equalsIgnoreCase(dataTypeName)
                || "sql_variant".equalsIgnoreCase(dataTypeName)
                || "sysname".equalsIgnoreCase(dataTypeName)
                || "uniqueidentifier".equalsIgnoreCase(dataTypeName)) {

            return 0;
        }

        if ("binary".equalsIgnoreCase(dataTypeName)
                || "char".equalsIgnoreCase(dataTypeName)
                || "datetime2".equalsIgnoreCase(dataTypeName)
                || "datetimeoffset".equalsIgnoreCase(dataTypeName)
                || "float".equalsIgnoreCase(dataTypeName)
                || "nchar".equalsIgnoreCase(dataTypeName)
                || "nvarchar".equalsIgnoreCase(dataTypeName)
                || "time".equalsIgnoreCase(dataTypeName)
                || "varbinary".equalsIgnoreCase(dataTypeName)
                || "varchar".equalsIgnoreCase(dataTypeName)
                || "xml".equalsIgnoreCase(dataTypeName)) {

            return 1;
        }

        return 2;
    }

    @Override
    public String escapeDataTypeName(String dataTypeName) {
        int indexOfPeriod = dataTypeName.indexOf('.');

        if (indexOfPeriod < 0) {
            if (!dataTypeName.startsWith(getQuotingStartCharacter())) {
                dataTypeName = escapeObjectName(dataTypeName, DatabaseObject.class);
            }

            return dataTypeName;
        }

        String schemaName = dataTypeName.substring(0, indexOfPeriod);
        if (!schemaName.startsWith(getQuotingStartCharacter())) {
            schemaName = escapeObjectName(schemaName, Schema.class);
        }

        dataTypeName = dataTypeName.substring(indexOfPeriod + 1, dataTypeName.length());
        if (!dataTypeName.startsWith(getQuotingStartCharacter())) {
            dataTypeName = escapeObjectName(dataTypeName, DatabaseObject.class);
        }

        return schemaName + "." + dataTypeName;
    }

    @Override
    public String unescapeDataTypeName(String dataTypeName) {
        int indexOfPeriod = dataTypeName.indexOf('.');

        if (indexOfPeriod < 0) {
            if (dataTypeName.matches("\\[[^]\\[]++\\]")) {
                dataTypeName = dataTypeName.substring(1, dataTypeName.length() - 1);
            }

            return dataTypeName;
        }

        String schemaName = dataTypeName.substring(0, indexOfPeriod);
        if (schemaName.matches("\\[[^]\\[]++\\]")) {
            schemaName = schemaName.substring(1, schemaName.length() - 1);
        }

        dataTypeName = dataTypeName.substring(indexOfPeriod + 1, dataTypeName.length());
        if (dataTypeName.matches("\\[[^]\\[]++\\]")) {
            dataTypeName = dataTypeName.substring(1, dataTypeName.length() - 1);
        }

        return schemaName + "." + dataTypeName;
    }

    @Override
    public String unescapeDataTypeString(String dataTypeString) {
        int indexOfLeftParen = dataTypeString.indexOf('(');
        if (indexOfLeftParen < 0) {
            return unescapeDataTypeName(dataTypeString);
        }

        return unescapeDataTypeName(dataTypeString.substring(0, indexOfLeftParen))
                + dataTypeString.substring(indexOfLeftParen);
    }

    /**
     * Determines if the SQL Server instance assigns Unicode data types (e.g. nvarchar) to strings.
     *
     * @return true if the SQL Server instance uses Unicode types by default, false if not.
     */
    public boolean sendsStringParametersAsUnicode() {
        if (sendsStringParametersAsUnicode == null) {
            try {
                if (getConnection() instanceof JdbcConnection) {
                    PreparedStatement ps = null;
                    ResultSet rs = null;
                    try {
                        String sql = "SELECT CONVERT([sysname], SQL_VARIANT_PROPERTY(?, 'BaseType'))";
                        ps = ((JdbcConnection) getConnection()).prepareStatement(sql);
                        ps.setString(1, "Liquibase");
                        rs = ps.executeQuery();
                        String baseType = null;
                        if (rs.next()) {
                            baseType = rs.getString(1);
                        }
                        // baseTypes starting with "n" can be something like nvarchar (or nchar)
                        sendsStringParametersAsUnicode =
                            (baseType == null) || baseType.startsWith("n");
                    } finally {
                        JdbcUtils.close(rs, ps);
                    }
                } else if (getConnection() instanceof OfflineConnection) {
                    sendsStringParametersAsUnicode =
                        ((OfflineConnection) getConnection()).getSendsStringParametersAsUnicode();
                }
            } catch (SQLException | DatabaseException e) {
                Scope.getCurrentScope().getLog(getClass()).warning(
                    LogType.LOG, "Cannot determine whether String parameters are sent as Unicode for MSSQL", e);
            }
        }

        return (sendsStringParametersAsUnicode == null) ? true : sendsStringParametersAsUnicode;
    }

    /**
     * Returns true if the connected MS SQL instance is a Microsoft Cloud ("Azure")-hosted instance of MSSQL.
     * @return true if instance runs in Microsoft Azure, false otherwise
     */
    public boolean isAzureDb() {
        return "Azure".equalsIgnoreCase(getEngineEdition());
    }

    /**
     * Determines the capabilities ("Edition") of the SQL Server database. Possible values are currently
     * "Personal", "Standard", "Enterprise" (Developer Edition is also reported as Enterprise), "Express" or "Azure".
     *
     * @return one of the strings above
     */
    public String getEngineEdition() {
        try {
            if (getConnection() instanceof JdbcConnection) {
                String sql = "SELECT CASE ServerProperty('EngineEdition')\n" +
                    "         WHEN 1 THEN 'Personal'\n" +
                    "         WHEN 2 THEN 'Standard'\n" +
                    "         WHEN 3 THEN 'Enterprise'\n" +
                    "         WHEN 4 THEN 'Express'\n" +
                    "         WHEN 5 THEN 'Azure'\n" +
                    "         ELSE 'Unknown'\n" +
                    "       END";
                return ExecutorService.getInstance().getExecutor(this)
                    .queryForObject(new RawSqlStatement(sql), String.class);
            }
        } catch (DatabaseException e) {
            Scope.getCurrentScope().getLog(getClass()).warning(LogType.LOG, "Could not determine engine edition", e);
        }
        return "Unknown";
    }

    @Override
    protected String getQuotingStartCharacter() {
        return "[";
    }

    @Override
    protected String getQuotingEndCharacter() {
        return "]";
    }

    @Override
    protected String getQuotingEndReplacement() {
        return "]]";
    }

    /*
    Source: https://docs.microsoft.com/en-us/sql/t-sql/language-elements/reserved-keywords-transact-sql?view=sql-server-2017
     */
    private static List<String> createReservedWordsCollection() {
        return Arrays.asList("ADD", "ALL", "ALTER", "AND", "ANY", "AS", "ASC", "AUTHORIZATION",
                "BACKUP", "BEGIN", "BETWEEN", "BREAK", "BROWSE", "BULK", "BY",
                "CASCADE", "CASE", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTERED", "COALESCE", "COLLATE",
                "COLUMN", "COMMIT", "COMPUTE", "CONSTRAINT", "CONTAINS", "CONTAINSTABLE", "CONTINUE",
                "CONVERT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE", "CURRENT_TIME",
                "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR",
                "DATABASE", "DBCC", "DEALLOCATE", "DECLARE", "DEFAULT", "DELETE", "DENY", "DESC",
                "DISK", "DISTINCT", "DISTRIBUTED", "DOUBLE", "DROP", "DUMP",
                "ELSE", "END", "ERRLVL", "ESCAPE", "EXCEPT", "EXEC", "EXECUTE", "EXISTS", "EXIT", "EXTERNAL",
                "FETCH", "FILE", "FILLFACTOR", "FOR", "FOREIGN", "FREETEXT", "FREETEXTTABLE",
                "FROM", "FULL", "FUNCTION",
                "GOTO", "GRANT", "GROUP",
                "HAVING", "HOLDLOCK",
                "IDENTITY", "IDENTITY_INSERT", "IDENTITYCOL", "IF", "IN", "INDEX",
                "INNER", "INSERT", "INTERSECT", "INTO", "IS",
                "JOIN",
                "KEY", "KILL",
                "LEFT", "LIKE", "LINENO", "LOAD",
                "MERGE",
                "NATIONAL", "NOCHECK", "NONCLUSTERED", "NOT", "NULL", "NULLIF",
                "OF", "OFF", "OFFSETS", "ON", "OPEN", "OPENDATASOURCE", "OPENQUERY", "OPENROWSET",
                "OPENXML", "OPTION", "OR", "ORDER", "OUTER", "OVER",
                "PERCENT", "PIVOT", "PLAN", "PRECISION", "PRIMARY", "PRINT", "PROC", "PROCEDURE", "PUBLIC",
                "RAISERROR", "READ", "READTEXT", "RECONFIGURE", "REFERENCES",
                "REPLICATION", "RESTORE", "RESTRICT", "RETURN", "REVERT", "REVOKE",
                "RIGHT", "ROLLBACK", "ROWCOUNT", "ROWGUIDCOL", "RULE",
                "SAVE", "SCHEMA", "SECURITYAUDIT", "SELECT", "SEMANTICKEYPHRASETABLE",
                "SEMANTICSIMILARITYDETAILSTABLE", "SEMANTICSIMILARITYTABLE", "SESSION_USER",
                "SET", "SETUSER", "SHUTDOWN", "SOME", "STATISTICS", "SYSTEM_USER",
                "TABLE", "TABLESAMPLE", "TEXTSIZE", "THEN", "TO", "TOP", "TRAN", "TRANSACTION",
                "TRIGGER", "TRUNCATE", "TRY_CONVERT", "TSEQUAL",
                "UNION", "UNIQUE", "UNPIVOT", "UPDATE", "UPDATETEXT", "USE", "USER",
                "VALUES", "VARYING", "VIEW",
                "WAITFOR", "WHEN", "WHERE", "WHILE", "WITH", "WITHIN GROUP", "WRITETEXT");
    }
}


package liquibase.util.csv.opencsv.bean;

import java.util.HashMap;
import java.util.Map;

/**
 * Copyright 2007,2010 Kyle Miller.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Expands on HeaderColumnNameMappingStrategy by allowing the user to pass in a map of column names to
 * bean names.  This way the fields in the bean do not have to match the fields in the csv file.
 *
 * @param <T> - class to be mapped.
 */
public class HeaderColumnNameTranslateMappingStrategy<T> extends HeaderColumnNameMappingStrategy<T> {
   private Map<String, String> columnMapping = new HashMap<>();

   /**
    * Default constructor.
    */
   public HeaderColumnNameTranslateMappingStrategy() {
   }

   /**
    * Retrieves the column name for a given column position
    *
    * @param col - column position.
    * @return - The column name.
    */
   @Override
   public String getColumnName(int col) {
      return (col < header.length) ? columnMapping.get(header[col].toUpperCase()) : null;
   }

   /**
    * retrieves the column mappings of the strategy.
    * @return - the column mappings of the strategy.
    */
   public Map<String, String> getColumnMapping() {
      return columnMapping;
   }

   /**
    * Sets the column mapping to those passed in.
    * @param columnMapping - source column mapping.
    */
   public void setColumnMapping(Map<String, String> columnMapping) {
      this.columnMapping.clear();
      for (Map.Entry<String, String> entry : columnMapping.entrySet()) {
         this.columnMapping.put(entry.getKey().toUpperCase(), entry.getValue());
      }
   }
}


package liquibase.configuration;

import liquibase.exception.UnexpectedLiquibaseException;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Base class for configuration classes used by {@link liquibase.configuration.LiquibaseConfiguration}.
 * Implementations must have a no-arg constructor for LiquibaseConfiguration to initialize them as needed.
 *

 * AbstractConfigurationContainer implementations contain a "namespace" which can be used as the prefix to system properties or cases where there may be name conflicts.
 *

 * Properties can be accessed by name using the {@link #getValue(String, Class)} method, but implementation should implement standard get/set methods for easier use.
 */
public abstract class AbstractConfigurationContainer implements ConfigurationContainer {

    private ConfigurationContainer configurationContainer;

    /**
     * Subclasses must call this constructor passing the namespace, but must themselves provide a no-arg public constructor.
     */
    protected AbstractConfigurationContainer(String namespace) {
        this.configurationContainer = new ConfigurationContainer(namespace);
    }

    protected ConfigurationContainer getContainer() {
        return configurationContainer;
    }

    /**
     * Return the ConfigurationProperty object for the given property name.
     * Normally {@link #getValue(String, Class)} is the easiest method to call.
     */
    @Override
    public ConfigurationProperty getProperty(String propertyName) {
        return getContainer().getProperty(propertyName);
    }

    /**
     * Return all available properties.
     */
    @Override
    public Set<ConfigurationProperty> getProperties() {
        return new HashSet<>(getContainer().properties.values());
    }

    /**
     * Returns the value for the given property cast to the passed returnType.
     * If the type of the property and the given return type are not compatible an exception will be thrown.
     * If the passed propertyName is not a defined property, an exception is thrown.
     */
    @Override
    public <T> T getValue(String propertyName, Class<T> returnType) {
        return getContainer().getValue(propertyName, returnType);
    }

    /**
     * Override default values for properties with the given ConfigurationProviders.
     */
    @Override
    public void init(ConfigurationValueProvider... configurationValueProviders) {
        if (configurationValueProviders != null) {
            for (ConfigurationProperty property : getContainer().properties.values()) {
                property.init(configurationValueProviders);
            }
        }
    }

    @Override
    public void setValue(String propertyName, Object value) {
      getContainer().setValue(propertyName, value);
    }

    /**
     * Like a java.util.Map, but with extra logic for working with ConfigurationProperties.
     * Used to define and hold available properties. Methods return "this" to allow easy chaining.
     */
    protected static class ConfigurationContainer {

        private final String namespace;
        private final Map<String, ConfigurationProperty> properties = new HashMap<>();

        protected ConfigurationContainer(String namespace) {
            this.namespace = namespace;
        }

        /**
         * Adds a property definition to this configuration.
         */
        public ConfigurationProperty addProperty(String propertyName, Class type) {
            ConfigurationProperty property = new ConfigurationProperty(namespace, propertyName, type);
            properties.put(propertyName, property);

            return property;
        }

        /**
         * Returns the ConfigurationProperty object with the given name. If the property was not defined, an exception is thrown.
         */
        public ConfigurationProperty getProperty(String propertyName) {
            ConfigurationProperty property = properties.get(propertyName);
            if (property == null) {
                throw new UnexpectedLiquibaseException("Unknown property on "+getClass().getName()+": "+propertyName);
            }

            return property;
        }

        /**
         * Returns the value for the given property. If the property was not defined, an exception is thrown.
         */
        public <T> T getValue(String propertyName, Class<T> returnType) {
            ConfigurationProperty property = getProperty(propertyName);

            if (!property.getType().isAssignableFrom(returnType)) {
                throw new UnexpectedLiquibaseException("Property "+propertyName+" on "+getClass().getName()+" is of type "+property.getType().getName()+", not "+returnType.getName());
            }

            return (T) property.getValue();
        }

        /**
         * Sets the value for the given property.
         * Any value set through this method will overwrite any default values found by the configured ConfigurationPropertyProviders.
         * If the property was not defined, an exception is thrown.
         */
        public void setValue(String propertyName, Object value) {
            ConfigurationProperty property = properties.get(propertyName);

            if (property == null) {
                throw new UnexpectedLiquibaseException("Unknown property on "+getClass().getName()+": "+propertyName);
            }

            property.setValue(value);

        }
    }

}


/**
 *
 */
package liquibase.database.core;

import liquibase.CatalogAndSchema;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.database.jvm.SybaseASAConnection;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.structure.core.Index;

import java.math.BigInteger;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * Support for SAP (formely: Siebel) AS (Adapter Server) Anywhere embedded/mobile database.
 *
 * @author otaranenko
 *
 */
public class SybaseASADatabase extends AbstractJdbcDatabase {

    private static final Set<String> systemTablesAndViews;

    static {
        systemTablesAndViews = new HashSet<>();
        systemTablesAndViews.add("dummy");
        systemTablesAndViews.add("sysarticle");
        systemTablesAndViews.add("sysarticlecol");
        systemTablesAndViews.add("sysarticlecols");
        systemTablesAndViews.add("sysarticles");
        systemTablesAndViews.add("sysattribute");
        systemTablesAndViews.add("sysattributename");
        systemTablesAndViews.add("syscapabilities");
        systemTablesAndViews.add("syscapability");
        systemTablesAndViews.add("syscapabilityname");
        systemTablesAndViews.add("syscatalog");
        systemTablesAndViews.add("syscolauth");
        systemTablesAndViews.add("syscollation");
        systemTablesAndViews.add("syscollationmappings");
        systemTablesAndViews.add("syscolperm");
        systemTablesAndViews.add("syscolstat");
        systemTablesAndViews.add("syscolstats");
        systemTablesAndViews.add("syscolumn");
        systemTablesAndViews.add("syscolumns");
        systemTablesAndViews.add("sysdomain");
        systemTablesAndViews.add("sysevent");
        systemTablesAndViews.add("syseventtype");
        systemTablesAndViews.add("sysextent");
        systemTablesAndViews.add("sysexternlogins");
        systemTablesAndViews.add("sysfile");
        systemTablesAndViews.add("sysfkcol");
        systemTablesAndViews.add("sysforeignkey");
        systemTablesAndViews.add("sysforeignkeys");
        systemTablesAndViews.add("sysgroup");
        systemTablesAndViews.add("sysgroups");
        systemTablesAndViews.add("sysindex");
        systemTablesAndViews.add("sysindexes");
        systemTablesAndViews.add("sysinfo");
        systemTablesAndViews.add("sysixcol");
        systemTablesAndViews.add("sysjar");
        systemTablesAndViews.add("sysjarcomponent");
        systemTablesAndViews.add("sysjavaclass");
        systemTablesAndViews.add("syslogin");
        systemTablesAndViews.add("sysoptblock");
        systemTablesAndViews.add("sysoption");
        systemTablesAndViews.add("sysoptions");
        systemTablesAndViews.add("sysoptjoinstrategy");
        systemTablesAndViews.add("sysoptorder");
        systemTablesAndViews.add("sysoptorders");
        systemTablesAndViews.add("sysoptplans");
        systemTablesAndViews.add("sysoptquantifier");
        systemTablesAndViews.add("sysoptrequest");
        systemTablesAndViews.add("sysoptrewrite");
        systemTablesAndViews.add("sysoptstat");
        systemTablesAndViews.add("sysoptstrategies");
        systemTablesAndViews.add("sysprocauth");
        systemTablesAndViews.add("sysprocedure");
        systemTablesAndViews.add("sysprocparm");
        systemTablesAndViews.add("sysprocparms");
        systemTablesAndViews.add("sysprocperm");
        systemTablesAndViews.add("syspublication");
        systemTablesAndViews.add("syspublications");
        systemTablesAndViews.add("sysremoteoption");
        systemTablesAndViews.add("sysremoteoptions");
        systemTablesAndViews.add("sysremoteoptiontype");
        systemTablesAndViews.add("sysremotetype");
        systemTablesAndViews.add("sysremotetypes");
        systemTablesAndViews.add("sysremoteuser");
        systemTablesAndViews.add("sysremoteusers");
        systemTablesAndViews.add("sysschedule");
        systemTablesAndViews.add("sysservers");
        systemTablesAndViews.add("syssqlservertype");
        systemTablesAndViews.add("syssubscription");
        systemTablesAndViews.add("syssubscriptions");
        systemTablesAndViews.add("syssync");
        systemTablesAndViews.add("syssyncdefinitions");
        systemTablesAndViews.add("syssyncpublicationdefaults");
        systemTablesAndViews.add("syssyncs");
        systemTablesAndViews.add("syssyncsites");
        systemTablesAndViews.add("syssyncsubscriptions");
        systemTablesAndViews.add("syssynctemplates");
        systemTablesAndViews.add("syssyncusers");
        systemTablesAndViews.add("systabauth");
        systemTablesAndViews.add("systable");
        systemTablesAndViews.add("systableperm");
        systemTablesAndViews.add("systrigger");
        systemTablesAndViews.add("systriggers");
        systemTablesAndViews.add("systypemap");
        systemTablesAndViews.add("sysuserauth");
        systemTablesAndViews.add("sysuserlist");
        systemTablesAndViews.add("sysusermessages");
        systemTablesAndViews.add("sysuseroptions");
        systemTablesAndViews.add("sysuserperm");
        systemTablesAndViews.add("sysuserperms");
        systemTablesAndViews.add("sysusertype");
        systemTablesAndViews.add("sysviews");
    }

    /**
     *
     */
    public SybaseASADatabase() {
        super();
        super.setCurrentDateTimeFunction("now()");
        super.unmodifiableDataTypes.addAll(Arrays.asList("integer", "bigint"));

    }

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

    /* (non-Javadoc)
     * @see liquibase.database.Database#getDefaultDriver(java.lang.String)
     */
    @Override
    public String getDefaultDriver(String url) {
        if (url.startsWith("jdbc:sybase")) {
            return "com.sybase.jdbc4.jdbc.SybDriver";
        } else {
            return null;
        }
    }

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

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

    /* (non-Javadoc)
    * @see liquibase.database.Database#getShortName()
    */
    @Override
    public String getShortName() {
        return "asany";
    }

    /* (non-Javadoc)
     * @see liquibase.database.Database#isCorrectDatabaseImplementation(java.sql.Connection)
     */
    @Override
    public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
        return "Adaptive Server Anywhere".equalsIgnoreCase(conn.getDatabaseProductName())
                || "SQL Anywhere".equalsIgnoreCase(conn.getDatabaseProductName())
                || "Adaptive Server IQ".equalsIgnoreCase(conn.getDatabaseProductName())
                || "Sybase IQ".equalsIgnoreCase(conn.getDatabaseProductName());
    }

    @Override
    public String getDefaultCatalogName() {
        try {
            DatabaseConnection connection = getConnection();
            if (connection == null) {
                return null;
            }
            return connection.getCatalog();
        } catch (DatabaseException e) {
            throw new UnexpectedLiquibaseException(e);
        }
    }

    @Override
    protected String getConnectionSchemaName() {
        try {
            if (getConnection() instanceof OfflineConnection) {
                return "dba";
            } else {
                Connection connection = ((JdbcConnection) getConnection()).getWrappedConnection();
                return ((connection == null) ? null : connection.getMetaData().getUserName());
            }
        } catch (SQLException e) {
            throw new UnexpectedLiquibaseException(e);
        }
    }

    @Override
    public String getViewDefinition(CatalogAndSchema schema, String viewName)
            throws DatabaseException {
        return super.getViewDefinition(schema, viewName);
    }

    /* (non-Javadoc)
     * @see liquibase.database.Database#supportsInitiallyDeferrableColumns()
     */
    @Override
    public boolean supportsInitiallyDeferrableColumns() {
        return false;
    }

    @Override
    public boolean supportsDropTableCascadeConstraints() {
        return false;
    }

    /* (non-Javadoc)
         * @see liquibase.database.Database#supportsTablespaces()
         */
    @Override
    public boolean supportsTablespaces() {
        return true;
    }

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

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

    /* (non-Javadoc)
     * @see liquibase.database.AbstractJdbcDatabase#getAutoIncrementClause()
     */
    @Override
    protected String getAutoIncrementClause() {
        return "DEFAULT AUTOINCREMENT";
    }

    @Override
    protected boolean generateAutoIncrementStartWith(BigInteger startWith) {
        // not supported
        return false;
    }

    @Override
    protected boolean generateAutoIncrementBy(BigInteger incrementBy) {
        // not supported
        return false;
    }

    @Override
    public void setAutoCommit(boolean b) throws DatabaseException {
        // workaround for strange Sybase bug.
        // In some circumstances tds-driver thrown exception
        // JZ016: The AutoCommit option is already set to false.
        if (b || super.isAutoCommit()) {
            super.setAutoCommit(b);
        }
    }

    @Override
    public String getJdbcCatalogName(CatalogAndSchema schema) {
        return "";
    }

    @Override
    public String escapeIndexName(String catalogName, String schemaName, String indexName) {
        /*
         * https://help.sap.com/viewer/40c01c3500744c85a02db71276495de5/17.0/en-US/816be9016ce210148874e67c83ca6c67.html
         * "There is no way of specifying the index owner in the CREATE INDEX statement. Indexes are always owned by
         * the owner of the table or materialized view."
         *
         * As a consequence, we will always remove the index owner.
         */
        return escapeObjectName(indexName, Index.class);
    }

    @Override
    public void setConnection(DatabaseConnection conn) {
        DatabaseConnection dbConn;
        if (conn instanceof JdbcConnection) {
            // If conn is a real connection (JDBC), wrap it to prevent a driver bug
            // (see SysbaseASAConnection for details)
            dbConn = new SybaseASAConnection(((JdbcConnection) conn).getWrappedConnection());
        } else {
            dbConn = conn;
        }
        super.setConnection(dbConn);
    }

    @Override
    public boolean requiresExplicitNullForColumns() {
        /* SAP Adaptive Server Enterprise and, by extension, SQL Anywhere in ASE compatiblity mode have the
         * strange requirement of setting the nullability of a column to NOT NULL if neither NULL nor
         * NOT NULL are specified. See:
         * http://dcx.sap.com/index.html#sqla170/en/html/819378356ce21014a17f8d51529119ee.html
         */
        return true;
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package liquibase.util.file;

import java.io.Serializable;

/**
 * Enumeration of IO case sensitivity.
 *

 * Different filing systems have different rules for case-sensitivity.
 * Windows is case-insensitive, Unix is case-isSensitive.
 *

 * This class captures that difference, providing an enumeration to
 * control how filename comparisons should be performed. It also provides
 * methods that use the enumeration to perform comparisons.
 *

 * Wherever possible, you should use the check methods in this
 * class to compare filenames.
 *
 * @author Stephen Colebourne
 * @version $Id: IOCase.java 606345 2007-12-21 23:43:01Z ggregory $
 * @since Commons IO 1.3
 */
final class IOCase implements Serializable {

    /**
     * The constant for case isSensitive regardless of operating system.
     */
    public static final IOCase SENSITIVE = new IOCase("Sensitive", true);

    /**
     * The constant for case insensitive regardless of operating system.
     */
    public static final IOCase INSENSITIVE = new IOCase("Insensitive", false);

    /**
     * The constant for case sensitivity determined by the current operating system.
     * Windows is case-insensitive when comparing filenames, Unix is case-isSensitive.
     *

     * If you derialize this constant of Windows, and deserialize on Unix, or vice
     * versa, then the value of the case-sensitivity flag will change.
     */
    public static final IOCase SYSTEM = new IOCase("System", !FilenameUtils.isSystemWindows());

    /** Serialization version. */
    private static final long serialVersionUID = -6343169151696340687L;

    /** The enumeration name. */
    private final String name;

    /** The sensitivity flag. */
    private final transient boolean isSensitive;

    //-----------------------------------------------------------------------
    /**
     * Factory method to create an IOCase from a name.
     *
     * @param name  the name to find
     * @return the IOCase object
     * @throws IllegalArgumentException if the name is invalid
     */
    public static IOCase forName(String name) {
        if (IOCase.SENSITIVE.name.equals(name)){
            return IOCase.SENSITIVE;
        }
        if (IOCase.INSENSITIVE.name.equals(name)){
            return IOCase.INSENSITIVE;
        }
        if (IOCase.SYSTEM.name.equals(name)){
            return IOCase.SYSTEM;
        }
        throw new IllegalArgumentException("Invalid IOCase name: " + name);
    }

    //-----------------------------------------------------------------------
    /**
     * Private constructor.
     *
     * @param name  the name
     * @param isSensitive  the sensitivity
     */
    private IOCase(String name, boolean isSensitive) {
        this.name = name;
        this.isSensitive = isSensitive;
    }

    /**
     * Replaces the enumeration from the stream with a real one.
     * This ensures that the correct flag is set for SYSTEM.
     *
     * @return the resolved object
     */
    private Object readResolve() {
        return forName(name);
    }

    //-----------------------------------------------------------------------
    /**
     * Gets the name of the constant.
     *
     * @return the name of the constant
     */
    public String getName() {
        return name;
    }

    /**
     * Does the object represent case isSensitive comparison.
     *
     * @return true if case isSensitive
     */
    public boolean isCaseSensitive() {
        return isSensitive;
    }

    //-----------------------------------------------------------------------
    /**
     * Compares two strings using the case-sensitivity rule.
     *

     * This method mimics {@link String#compareTo} but takes case-sensitivity
     * into account.
     *
     * @param str1  the first string to compare, not null
     * @param str2  the second string to compare, not null
     * @return true if equal using the case rules
     * @throws NullPointerException if either string is null
     */
    public int checkCompareTo(String str1, String str2) {
        if ((str1 == null) || (str2 == null)) {
            throw new NullPointerException("The strings must not be null");
        }
        return isSensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2);
    }

    /**
     * Compares two strings using the case-sensitivity rule.
     *

     * This method mimics {@link String#equals} but takes case-sensitivity
     * into account.
     *
     * @param str1  the first string to compare, not null
     * @param str2  the second string to compare, not null
     * @return true if equal using the case rules
     * @throws NullPointerException if either string is null
     */
    public boolean checkEquals(String str1, String str2) {
        if ((str1 == null) || (str2 == null)) {
            throw new NullPointerException("The strings must not be null");
        }
        return isSensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
    }

    /**
     * Checks if one string starts with another using the case-sensitivity rule.
     *

     * This method mimics {@link String#startsWith(String)} but takes case-sensitivity
     * into account.
     *
     * @param str  the string to check, not null
     * @param start  the start to compare against, not null
     * @return true if equal using the case rules
     * @throws NullPointerException if either string is null
     */
    public boolean checkStartsWith(String str, String start) {
        return str.regionMatches(!isSensitive, 0, start, 0, start.length());
    }

    /**
     * Checks if one string ends with another using the case-sensitivity rule.
     *

     * This method mimics {@link String#endsWith} but takes case-sensitivity
     * into account.
     *
     * @param str  the string to check, not null
     * @param end  the end to compare against, not null
     * @return true if equal using the case rules
     * @throws NullPointerException if either string is null
     */
    public boolean checkEndsWith(String str, String end) {
        int endLen = end.length();
        return str.regionMatches(!isSensitive, str.length() - endLen, end, 0, endLen);
    }

    /**
     * Checks if one string contains another at a specific index using the case-sensitivity rule.
     *

     * This method mimics parts of {@link String#regionMatches(boolean, int, String, int, int)}
     * but takes case-sensitivity into account.
     *
     * @param str  the string to check, not null
     * @param strStartIndex  the index to start at in str
     * @param search  the start to search for, not null
     * @return true if equal using the case rules
     * @throws NullPointerException if either string is null
     */
    public boolean checkRegionMatches(String str, int strStartIndex, String search) {
        return str.regionMatches(!isSensitive, strStartIndex, search, 0, search.length());
    }

    /**
     * Converts the case of the input String to a standard format.
     * Subsequent operations can then use standard String methods.
     *
     * @param str  the string to convert, null returns null
     * @return the lower-case version if case-insensitive
     */
    String convertCase(String str) {
        if (str == null) {
            return null;
        }
        return isSensitive ? str : str.toLowerCase();
    }

    //-----------------------------------------------------------------------
    /**
     * Gets a string describing the sensitivity.
     *
     * @return a string describing the sensitivity
     */
    public String toString() {
        return name;
    }

}