<?php

namespace Api\PhpUtils\Mysql;

use Api\PhpUtils\Mon\MonUtil;
use Api\PhpUtils\Mysql\Medoo;
use Api\PhpUtils\Log\FileLog;

/**
 * MySQL基类,支持分库分表
 * 
 * 1. MySQL配置信息在yaconf的mysql. DATABASE_NAME, TABLE_NAME, CONFIG_INDEX 在子类中设置
 * 
 * 2. 数据库分库分表，需要发号器全局唯一id，不需要分库分表请使用MysqlBase基类
 * 
 * 3. 提供常用的CURD方法，共他方法会调用 __callStatic()方法
 * 
 * 4. 返回值说明：
 *    return false  是校验参数错误，数据库执行错误, debug模式等异常的标识，错误信息请看日志，
 *                  使用error()方法可以获取最后的一条错误, 对错误进行了一次强制重试，
 *    return []     查询结果为空
 *    return number 插入为lasterInsertId:String 按需自己intval()，更新和删除返回影响条数
 * 
 * 5. 根据镜像中php的版本选择使用version1.7.10 版本的Medoo类，并进行了改造，去掉了join操作，增加了$options参数；
 * @see https://medoo.in  (英文网站)
 * @see https://medoo.lvtao.net (中文网站)
 */

abstract class MysqlClusterBase
{
    const DATABASE_TYPE = 'mysql'; //数据库类型
    const TABLE_NAME = ''; //表名，必填
    const DATABASE_NAME = ''; //数据库名称，必填
    const CONFIG_INDEX = ''; //yaconf中的index索引，必填
    const SHARDING_DATABASE = true; // 库分片标识
    const SHARDING_TABLE = false; // 表分片标识

    const SHARDING_DATABASE_COLUMN = ''; // 数据库分片字段order_id
    const SHARDING_DATABASE_ALGORITHM = ''; // 数据库分片算法策略mod(-6,-5)
    const SHARDING_DATABASE_COMPAT_COLUMN = ''; // 兼容数据库分片字段user_id
    const SHARDING_DATABASE_COMPAT_ALGORITHM = ''; // 兼容数据库分片算法表达式mod(-2,-1)

    const SHARDING_TABLE_COLUMN = ''; // 表分片字段order_id
    const SHARDING_TABLE_ALGORITHM = ''; // 表分片算法表达式DateM(0,-6)
    // const SHARDING_TABLE_COMPAT_COLUMN = ''; // 兼容表分片字段user_id
    // const SHARDING_TABLE_COMPAT_ALGORITHM = ''; // 兼容表分片算法表达式DateM(0,-6)

    const WRITE = 'write';
    const READ = 'read';
    const PRIMARY_KEY = 'id';

    /**
     * yaconf中的数据库配置项
     *
     * @var array
     */
    private static $iniConf = [];

    /**
     * 数据库配置
     *
     * @var array
     */
    private static $dbConf = [];

    /**
     * 数据库连接信息，分主从
     *
     * @var array
     */
    private static $dbConnect = [];

    /**
     * 数据库当前连接信息
     *
     * @var Medoo\Medoo
     */
    private static $dbCurrentConnect = null;

    /**
     * 数据库当前事务索引
     *
     * @var integer
     */
    private static $transationIndex = 0;

    /**
     * 数据库分片数
     * 
     * @var integer
     */
    private static $dbShardingCount = 1;

    /**
     * 数据库分片列对应值
     *
     * @var integer
     */
    private static $dbShardingColumnValue = 0;

    /**
     * 数据库分片兼容列对应值
     *
     * @var integer
     */
    private static $dbShardingCompatColumnValue = 0;

    /**
     * 数据库查询等的返回结果
     *
     * @var mixed
     */
    private static $sqlResut = [];

    /**
     * 数据库最后一个
     *
     * @var array
     */
    private static $errorInfo = [];

    /**
     * 写操作函数
     *
     * @var array
     */
    protected static $writeFuncitonList = [
        "insert",
        "update",
        "delete",
        "selectMaster", // 或使用/*master*/强制路由，对主从实时性要求较高的场景使用，分布式系统尽量用消息代替查主库等其他方案
        "getMaster",
        "selectForUpdate",
        "insertDuplicate",
    ];

    /**
     * 读操作函数
     *
     * @var array
     */
    protected static $readFunctionList = [
        "select",
        "get",
        "count",
    ];

    public static function __callStatic($method, $arguments)
    {
        // 执行SQL前，先做基础的验证
        if (!self::validation($method, $arguments)) {
            return false;
        }

        // 解析参数中数据库分片值
        if (!self::parseDatabaseShardingColumnValue($method, $arguments)) {
            return false;
        }

        // 读取配置文件内容
        self::loadCfg();

        // 获取库分片索引
        $dbShardingIndex = self::getDatabaseShardingIndex();

        if (in_array($method, self::$writeFuncitonList)) {
            // 写操作
            $type = static::WRITE;
            self::$dbConnect[$type.':'.$dbShardingIndex] = self::getConnection($type, $dbShardingIndex);
            if (strpos($method, 'Master') !== false) {
                $method = substr_replace($method, '', -6);
            }
            array_unshift($arguments, static::TABLE_NAME);
        } elseif (in_array($method, self::$readFunctionList)) {
            // 读操作
            $type = static::READ;
            self::$dbConnect[$type.':'.$dbShardingIndex] = self::getConnection($type, $dbShardingIndex);
            array_unshift($arguments, static::TABLE_NAME);
        } else {
            throw new \Exception("use undefined Medoo function:" . $method . "-" . json_encode($arguments));
        }

        self::$dbCurrentConnect = self::$dbConnect[$type.':'.$dbShardingIndex];

        //增加mon 打点
        $start_time = microtime(true);
        self::$sqlResut = call_user_func_array([self::$dbConnect[$type.':'.$dbShardingIndex], $method], $arguments);
        
        if (!self::catchError($method, $arguments)) {
            // 如果失败重试一次
            self::$dbCurrentConnect = self::$dbConnect[$type.':'.$dbShardingIndex] = self::getConnection($type, $dbShardingIndex);
            self::$sqlResut = call_user_func_array([self::$dbConnect[$type.':'.$dbShardingIndex], $method], $arguments);
            if (!self::catchError($method, $arguments)) {
                return false;
            }
        }
        $end_time = microtime(true);
        list($dbServer, $dbPort) = self::getDbServerPort($type, $dbShardingIndex);
        MonUtil::dbMon($dbServer.'_'.$dbPort, $method, 'mysql', $end_time-$start_time);

        return self::formatResult($method, $arguments);
    }

    /**
     * 获取数据库连接
     *
     * @param [string] $type 读或写
     * @param boolean $flush 是否强制重新连接，重试等场景使用
     * @return
     */
    public static function getConnection($type, $dbShardingIndex, $flush = false)
    {
        if (!isset(self::$dbConnect[$type.':'.$dbShardingIndex]) || $flush) {
            self::$dbConnect[$type.':'.$dbShardingIndex] = new Medoo(self::getDbConf($type, $dbShardingIndex));
        }

        return self::$dbConnect[$type.':'.$dbShardingIndex];
    }

    /**
     * 获取数据库配置
     *
     * @param string $type 读或写
     * @return array
     */
    public static function getDbConf($type, $dbShardingIndex)
    {
        list($server, $port) = self::getDbServerPort($type, $dbShardingIndex);
        $options = self::getDbOptions();

        self::$dbConf[$server . ':' . $port] = [
            'database_type' => static::DATABASE_TYPE ? static::DATABASE_TYPE : self::$iniConf['database_type'],
            'database_name' => self::$iniConf['database_name'],
            'server'        => $server,
            'port'          => $port,
            'username'      => self::$iniConf['username'],
            'password'      => self::$iniConf['password'],
            'charset'       => self::$iniConf['charset'],
            'option'        => array_replace([
                \PDO::ATTR_STRINGIFY_FETCHES    => false,
                \PDO::ATTR_EMULATE_PREPARES     => false,
                \PDO::ATTR_CASE                 => \PDO::CASE_NATURAL,
                \PDO::ATTR_ORACLE_NULLS         => \PDO::NULL_TO_STRING,
                \PDO::ATTR_TIMEOUT              => intval(self::$iniConf['connect_timeout'] ?? 10)
            ], $options)
        ];

        return self::$dbConf[$server . ':' . $port];
    }

    /**
     * 获取数据库server和port
     *
     * @param string $type 读或写类型
     * @return void
     * @throws \Exception
     */
    private static function getDbServerPort($type, $dbShardingIndex)
    {
        self::loadCfg();
        $server = '';
        $port = 0;
        $host = self::$iniConf[$type]['host'];
        // 处理分为写读逻辑
        if ($type === static::WRITE) {
            if (!static::SHARDING_DATABASE || strpos($host, ',') === false) {
                // 主库未分片,但表分片了
                list($server, $port) = explode(':', $host);
            } else {
                // 主库已分片
                $hostList = explode(',', $host);
                // self::$dbShardingCount = count($hostList);
                // $dbShardingIndex = self::getDatabaseShardingIndex();
                $sharddingHost = $hostList[$dbShardingIndex];
                list($server, $port) = explode(':', $sharddingHost);
            }
        } elseif ($type === static::READ) {
            if (!static::SHARDING_DATABASE || count($host) === 1) {
                // 从库未分片,但表分片了
                if (strpos($host, ',') === false) {
                    list($server, $port) = explode(':', $host);
                } else {
                    $list = explode(',', $host);
                    $rand = array_rand($list);
                    list($server, $port) = explode(':', $list[$rand]);
                }
            } else {
                // 从库已分片，根据配置选择分片策略
                // self::$dbShardingCount = count($host);
                // $dbShardingIndex = self::getDatabaseShardingIndex();
                $slaveHost = $host[$dbShardingIndex];
                if (strpos($slaveHost, ',') === false) {
                    list($server, $port) = explode(':', $slaveHost);
                } else {
                    $list = explode(',', $slaveHost);
                    $rand = array_rand($list);
                    list($server, $port) = explode(':', $list[$rand]);
                }
            }
        }

        return [$server, $port];
    }

    /**
     * 获取数据库分片数量
     *
     * @return integer
     * @throws \Exception
     */
    private static function getDbShardingCount() {

        self::loadCfg();

        $host = self::$iniConf[static::WRITE]['host'];
        $hostList = explode(',', $host);
        self::$dbShardingCount = count($hostList);
    }

    /**
     * 获取数据库分片策略
     *
     * @return array
     */
    protected static function getDatabaseShardingIndex($dbShardingColumnValue = 0, $dbShardingCompatColumnValue = 0)
    {
        // 未使用分库
        if (!static::SHARDING_DATABASE) {
            return 0;
        }

        if(!empty($dbShardingColumnValue)) {
            self::$dbShardingColumnValue = $dbShardingColumnValue;
        }

        if(!empty($dbShardingCompatColumnValue)) {
            self::$dbShardingCompatColumnValue = $dbShardingCompatColumnValue;
        }

        self::getDbShardingCount();

        $dbShardingIndex = 0;
        if (self::$dbShardingColumnValue) {
            $modKey = self::$dbShardingColumnValue;
            if (static::SHARDING_DATABASE_COLUMN && static::SHARDING_DATABASE_ALGORITHM) {
                // 取模的分库策略
                if (stripos(static::SHARDING_DATABASE_ALGORITHM, 'mod') !== false) {
                    // 需要根据SHARDING_DATABASE_COLUMN配置字段截取字段
                    if (strpos(static::SHARDING_DATABASE_ALGORITHM, '(') !== false) {
                        $startEndString = substr(static::SHARDING_DATABASE_ALGORITHM, strpos(static::SHARDING_DATABASE_ALGORITHM, '(') + 1, -1);
                        if (strpos($startEndString, ',') !== false) {
                            // 有start,end
                            list($start, $end) = explode(',', $startEndString);
                            $modKey = substr(self::$dbShardingColumnValue, $start, $end);
                        } else {
                            // 只有start,无end
                            $modKey = substr(self::$dbShardingColumnValue, $startEndString);
                        }
                    }
                    // 计算mod取模后的值
                    $dbShardingIndex = intval($modKey) % self::$dbShardingCount;
                    return $dbShardingIndex;
                }
            } 
        }elseif(self::$dbShardingCompatColumnValue) {
            $modKey = self::$dbShardingCompatColumnValue;
            if (static::SHARDING_DATABASE_COMPAT_COLUMN && static::SHARDING_DATABASE_COMPAT_ALGORITHM) {
    
                // 取模的分库策略
                if (stripos(static::SHARDING_DATABASE_ALGORITHM, 'mod') !== false) {
    
                    // 需要根据SHARDING_DATABASE_COMPAT_COLUMN配置字段截取字段
                    if (strpos(static::SHARDING_DATABASE_ALGORITHM, '(') !== false) {
                        $startEndString = substr(static::SHARDING_DATABASE_ALGORITHM, strpos(static::SHARDING_DATABASE_ALGORITHM, '(') + 1, -1);
                        if (strpos($startEndString, ',') !== false) {
                            // 有start,end
                            list($start, $end) = explode(',', $startEndString);
                            $modKey = substr(self::$dbShardingCompatColumnValue, $start, $end);
                        } else {
                            // 只有start,无end
                            $modKey = substr(self::$dbShardingCompatColumnValue, $startEndString);
                        }
                    }
    
                    // 计算mod取模后的值
                    $dbShardingIndex = intval($modKey) % self::$dbShardingCount;
                    return $dbShardingIndex;
                }
            } 
        }
        // echo 'exp';
        throw new \Exception('mysql not get sharding database-stragegy');
    }

    /**
     * 获取ini中的option配置，必须用\PDO::ATTR_STRINGIFY_FETCHES对应的int数值
     *
     * @return array
     * @throws \Exception
     */
    public static function getDbOptions()
    {
        self::loadCfg();

        $options = [];
        if (isset(self::$iniConf['options'])) {
            foreach (self::$iniConf['options'] as $item) {
                $itemArr = explode('=>', $item);
                $options[trim($itemArr[0])] = $itemArr[1];
            }
        }
        return $options;
    }

    /**
     * 执行SQL前，先做基础的验证
     */
    private static function validation($method, &$arguments)
    {
        // 没有使用分片方式
        if (empty(static::SHARDING_DATABASE_COLUMN) && empty(static::SHARDING_TABLE_COLUMN)) {
            echo 'validation1';
            self::$errorInfo = ['validation', 0, "sql exception not use sharding mode :" . $method . "-" . json_encode($arguments)];
            FileLog::error("sql exception not use sharding mode:" . $method . "-" . json_encode($arguments));
            return false;
        }

        switch (strtolower($method)) {
            case 'update':
                // 更新操作前，如果没有where条件
                if (!isset($arguments[1]) || empty($arguments[1])) {
                    self::$errorInfo = ['validation', 0, "sql exception write without where:" . $method . "-" . json_encode($arguments)];
                    FileLog::error("sql exception write without where:" . $method . "-" . json_encode($arguments));
                    return false;
                }
                break;

            case 'delete':
                // 删除操作前，如果没有where条件
                if (!isset($arguments[0]) || empty($arguments[0])) {
                    self::$errorInfo = ['validation', 0, "sql exception write without where:" . $method . "-" . json_encode($arguments)];
                    FileLog::error("sql exception write without where:" . $method . "-" . json_encode($arguments));
                    return false;
                }
                // 如果必须要物理删除数据，只允计按主键或唯一键单条删除
                if (!isset($arguments[0][static::PRIMARY_KEY])) {
                    self::$errorInfo = ['validation', 0, "sql exception write error primary key:" . $method . "-" . json_encode($arguments)];
                    FileLog::error("sql exception write error primary key:" . $method . "-" . json_encode($arguments));
                    return false;
                }
                break;

            case 'select':
            case 'selectmaster':
            case 'get':
            case 'getmaster':
                // 读取没有limit的强制加limit
                if (!isset($arguments[1]['LIMIT'])) {
                    $arguments[1]['LIMIT'] = 5000;
                    return true;
                }
                break;

            default:
                break;
        }

        return true;
    }

    /**
     * 解析参数中数据库分片值
     *
     * @param string $method
     * @param array $arguments
     * @return bool
     */
    private static function parseDatabaseShardingColumnValue($method, $arguments)
    {
        // 未使用分库
        if (!static::SHARDING_DATABASE) {
            return true;
        }

        // 数据库分片，需要提取条件中的分片列及分片兼容列，比如order_id及user_id
        if (!empty(static::SHARDING_DATABASE_COLUMN) || !empty(static::SHARDING_DATABASE_COMPAT_COLUMN)) {
            switch (strtolower($method)) {
                case 'update':
                case 'select':
                case 'selectmaster':
                case 'get':
                case 'getmaster':
                    $where = $arguments[1]; //第二个参数
                    if (isset($where[static::SHARDING_DATABASE_COLUMN])) {
                        // echo $where[static::SHARDING_DATABASE_COLUMN];exit;
                        self::$dbShardingColumnValue = $where[static::SHARDING_DATABASE_COLUMN];
                    } elseif (isset($where[static::SHARDING_DATABASE_COMPAT_COLUMN])) {
                        self::$dbShardingCompatColumnValue = $where[static::SHARDING_DATABASE_COMPAT_COLUMN];
                    } elseif (isset($where['AND'][static::SHARDING_DATABASE_COLUMN])) {
                        // where 条件中只支持AND操作，不要使用OR操作
                        self::$dbShardingColumnValue = $where['AND'][static::SHARDING_DATABASE_COLUMN];
                    } elseif (isset($where['AND'][static::SHARDING_DATABASE_COMPAT_COLUMN])) {
                        self::$dbShardingCompatColumnValue = $where['AND'][static::SHARDING_DATABASE_COMPAT_COLUMN];
                    }
                    break;

                case 'delete':
                case 'count':
                    $where = $arguments[0] ?? []; //第一个参数
                    if (isset($where[static::SHARDING_DATABASE_COLUMN])) {
                        self::$dbShardingColumnValue = $where[static::SHARDING_DATABASE_COLUMN];
                    } elseif (isset($where[static::SHARDING_DATABASE_COMPAT_COLUMN])) {
                        self::$dbShardingCompatColumnValue = $where[static::SHARDING_DATABASE_COMPAT_COLUMN];
                    } elseif (isset($where['AND'][static::SHARDING_DATABASE_COLUMN])) {
                        self::$dbShardingColumnValue = $where['AND'][static::SHARDING_DATABASE_COLUMN];
                    } elseif (isset($where['AND'][static::SHARDING_DATABASE_COMPAT_COLUMN])) {
                        self::$dbShardingCompatColumnValue = $where['AND'][static::SHARDING_DATABASE_COMPAT_COLUMN];
                    }
                    break;

                case 'insert':
                    $data = $arguments[0]; // data中的值
                    // 单条
                    if (isset($data[static::SHARDING_DATABASE_COLUMN])) {
                        self::$dbShardingColumnValue = $data[static::SHARDING_DATABASE_COLUMN];
                    } else {
                        // 只支持插入多条同库中的数据
                        $key = array_unique(array_column($data, static::SHARDING_DATABASE_COLUMN));
                        if ($key && count($key) === 1) {
                            self::$dbShardingColumnValue = $data[0][static::SHARDING_DATABASE_COLUMN];
                        } else {
                            return false; // 暂不支持一次插入多个库中数据
                        }
                    }
                    break;
            }

            if (self::$dbShardingColumnValue || self::$dbShardingCompatColumnValue) {
                return true;
            }

            self::$errorInfo = ['parseDatabaseShardingColumnValue', 0, "not find sharding column:" . $method . "-" . json_encode($arguments)];
            FileLog::error("not find sharding column:" . $method . "-" . json_encode($arguments));
            return false;
        }

        // 使用数据库分片，未指定分片对应列返回false
        self::$errorInfo = ['parseDatabaseShardingColumnValue', 0, "not set sharding column:" . $method . "-" . json_encode($arguments)];
        FileLog::error("not set sharding column:" . $method . "-" . json_encode($arguments));
        return false;
    }

    /**
     * 捕获错误
     *
     * @return bool
     */
    private static function catchError($method, $arguments)
    {
        self::$errorInfo = call_user_func_array([self::$dbCurrentConnect, 'error'], []);
        // debug 模式返回值为NULL
        if (is_null(self::$errorInfo)) {
            return true;
        }

        list($sqlState, $errorCode, $errorMessage) = self::$errorInfo;
        // PDO::errorInfo() the SQLSTATE error code for '00000' (success) and '01000' (success with warning).
        if ($sqlState == '00000') {
            return true;
        }

        FileLog::error("mysql error catch:" . $method . "-" . json_encode($arguments) . '-' . print_r([$sqlState, $errorCode, $errorMessage], 1));

        //throw Exception
        // $msg = "mysql error catch:" . $method . "-" . json_encode($arguments) . '-' . json_encode([$sqlState, $errorCode, $errorMessage]);
        // throw new \Exception($msg);
        return false;
    }

    /**
     * @param bool $flush
     * @throws \Exception
     * 加载配置文件，支持强更新
     */
    private static function loadCfg($flush = false) {
        if($flush || empty(self::$iniConf)) {
            //重新加载 ini
            self::$iniConf = config('mysql', static::CONFIG_INDEX);
            if (!self::$iniConf) {
                throw new \Exception('mysql yaconf ini config not exist');
            }
        }
    }

    /**
     * 格式化返回结果
     *
     * @return mixed
     */
    private static function formatResult($method, $arguments)
    {
        // debug 模式errorInfo返回值为NULL
        if (is_null(self::$errorInfo)) {
            return self::$sqlResut;
        }

        // get未查询到结果返回NULL,select返回空数组[],统一转成空数组[]
        if (is_null(self::$sqlResut)) {
            return [];
        }

        switch (strtolower($method)) {
            case 'insert':
                // 多条数据插入，返回影响的行数
                if (isset($arguments[1][1])) {
                    // PDOStatement::rowCount() 返回上一个由对应的 PDOStatement 对象执行DELETE、 INSERT、或 UPDATE 语句受影响的行数。
                    return self::$sqlResut->rowCount();
                }
                // 对于无AUTO_INCREMENT的表，可以指定返回影响的行数的参数
                if (isset($arguments[2]['rowCount']) && $arguments[2]['rowCount']) {
                    // PDOStatement::rowCount() 返回上一个由对应的 PDOStatement 对象执行DELETE、 INSERT、或 UPDATE 语句受影响的行数。
                    return self::$sqlResut->rowCount();
                }
                // 单条默认插入返回最后插入行的lastInsertId，如果无AUTO_INCREMENT字段，返回NULL
                return call_user_func_array([self::$dbCurrentConnect, 'id'], []);
                break;


            case 'update':
            case 'delete':
                // 返回上一个由对应的 PDOStatement 对象执行DELETE、 INSERT、或 UPDATE 语句受影响的行数。
                return self::$sqlResut->rowCount();
                break;

            case 'select':
                // 返回数组
                return self::$sqlResut;
                break;

            default:
                return self::$sqlResut;
                break;
        }
    }

    /**
     * 事务操作(单库事务)
     *
     * @param int $dbShardingColumnValue
     * @return bool
     * @throws \Exception
     */
    public static function beginTransaction($dbShardingColumnValue = 0, $dbShardingCompatColumnValue = 0)
    {
        self::$dbShardingColumnValue = $dbShardingColumnValue;
        self::$dbShardingCompatColumnValue = $dbShardingCompatColumnValue;

        // 读取配置文件内容
        self::loadCfg();

        // 获取库分片索引
        self::$transationIndex = self::getDatabaseShardingIndex();

        $type = static::WRITE;
        self::$dbConnect[$type.':'.self::$transationIndex] = self::getConnection($type, self::$transationIndex);
        if(!self::$dbConnect[$type.':'.self::$transationIndex]->pdo->inTransaction()) {
            return self::$dbConnect[$type.':'.self::$transationIndex]->pdo->beginTransaction();
        }
        return false;
    }

    /**
     * 事务提交
     *
     * @return bool
     */
    public static function commit()
    {
        $type = static::WRITE;
        self::$dbConnect[$type.':'.self::$transationIndex] = self::getConnection($type, self::$transationIndex);
        if(self::$dbConnect[$type.':'.self::$transationIndex]->pdo->inTransaction()) {
            return self::$dbConnect[$type.':'.self::$transationIndex]->pdo->commit();
        }
        return false;
    }

    /**
     * 事务回滚
     *
     * @return bool
     */
    public static function rollback()
    {
        $type = static::WRITE;
        self::$dbConnect[$type.':'.self::$transationIndex] = self::getConnection($type, self::$transationIndex);
        if(self::$dbConnect[$type.':'.self::$transationIndex]->pdo->inTransaction()) {
            return self::$dbConnect[$type.':'.self::$transationIndex]->pdo->rollback();
        }
        return false;
    }

    /**
     * 事务操作，Medoo提供的
     *
     * @return mixed
     */
    public static function action(callable $actions)
    {
        $type = static::WRITE;
        self::$dbConnect[$type.':'.self::$transationIndex] = self::getConnection($type, self::$transationIndex);
        return self::$dbConnect[$type.':'.self::$transationIndex]->action($actions);
    }

    /**
     * 返回最后一条执行的SQL语句
     * 
     * @return array
     */
    public static function last()
    {
        return self::$dbCurrentConnect->last();
    }

    /**
     * 获得最后一个执行的错误
     *
     * @return array
     */
    public static function error()
    {
        return self::$errorInfo;
    }

    /**
     * 返回所有执行的查询
     *
     * @return array
     */
    public static function log()
    {
        return self::$dbCurrentConnect->log();
    }

    /**
     * 获取所有分片连接
     *
     * @return array
     * @throws \Exception
     */
    public static function getWriteConnectionList()
    {
        // 获取库分片数量
        self::getDbShardingCount();

        $type = static::WRITE;
        for ($i=0; $i < self::$dbShardingCount; $i++) { 
            self::$dbConnect[$type.':'.$i] = self::getConnection($type, $i);
        }
        return self::$dbConnect;
    }

    /**
     * 获取所有分片连接
     *
     * @return array
     * @throws \Exception
     */
    public static function getReadConnectionList()
    {
        // 获取库分片数量
        self::getDbShardingCount();

        $type = static::READ;
        for ($i=0; $i < self::$dbShardingCount; $i++) { 
            self::$dbConnect[$type.':'.$i] = self::getConnection($type, $i);
        }
        return self::$dbConnect;
    }
}
