<?php

use function PHPSTORM_META\type;

/**
* 数据库类
* 
* @version 2.0.0
*/

class pdoHelper
{
    protected $pdo = null;
    protected $config = [];
    protected $dbkey = '';
    /** 查询结果集 */
    protected $statement = false;
    /** 最后一次执行的SQL */
    protected $lastSql = '';
    /** 是否发错SQL查询错误 */
    protected $hasError = false;
    /** 事务最先开启者 */
    protected $tranStartMethod = '';
    /** 是否触发了事务回滚 */
    protected $hasTriggerRollBack = false;
    /** 当前是否使用了mysql缓存结果集方式 */
    protected $useBuffQuery = true; 
    /** 结果集用来做值的字段 */
    protected $resultSetKeyFields = null;

    /**
     * 构造
     *
     * @param string $key
     * @param array $configArr
     */
	public function __construct($key,$configArr)
    {
        $this->dbkey = $key;
        $this->config = $configArr;
    }

    /**
     * 执行PDO 原生SQL查询
     *
     * @param string $sql
     * @return self
     */
    public function query(string $sql):self
    {
        //preg_match("/\binsert|update|delete\b|[\'|\"|`][^'\"`]+[\'|\"|`](*SKIP)(*FAIL)/i",strtr($sql,[' ON UPDATE '=>'',' ON DELETE '=>'']),$m);
        preg_match("/\binsert|update|delete\b|[\'|\"|`][^'\"`]+[\'|\"|`](*SKIP)(*FAIL)/i",$sql,$m);
        if($m){
            throw new \Exception(slg("query()不支持,INSERT,UPDATE,DELETE语句"));
        }
        return $this->command('query', $sql);
    }

    /**
     * 执行PDO 原生SQL查询
     *
     * @param string $sql
     * @return integer 返回受影响的行数
     */
    public function exec(string $sql)
    {
        return $this->command('exec', $sql);
    }

    /**
     * 执行PDO 原生SQL查询
     *
     * @param string $type query或exec
     * @param string $sql
     * @return self:int $type=query返回db对象, $type=exec返回受影响的行数
     */
    private function command(string $type, string $sql)
    {
        if($this->hasTriggerRollBack){
            //如果标记了事务回滚,不再执行SQL
            return $this;
        }
        $this->connect();
        $this->lastSql = $sql;
        $this->statement = false;
        try {
            if($type=='query'){
                $this->statement = $this->pdo->query($sql);
            }else{
                //$type=exec 直接返回受影响的行数
                return $this->pdo->exec($sql);
            }
        } catch (\PDOException $e) {
            $this->hasError = true;
            if($this->tranStartMethod!=''){
                $this->pdo->rollBack();
            }
            $this->log($e->getMessage());
        }
		return $this;
    }
    
    /**
     * 设置mysql缓存结果集方式
     *
     * @return void
     */
	protected function set_mysql_use_buffered_query(bool $value){
        $this->useBuffQuery = $value;
        //是否使用mysql缓存结果集方式, 
        //true  如果使用的话大表大数据处理会存在内存溢出问题,默认使用
        //false 不使用的话，在循环遍历数据结束前不允许再执行sql查询
        $this->connect();
        $this->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $value);
    }    
    
    /**
     * 获取 mysql缓存用户结果集是否开启
     *
     * @return void
     */
	public function getUserBuff():bool{
        return $this->useBuffQuery;
    }    
    
    /**
     * 开启 mysql缓存结果集方式
     *
     * @return void
     */
	public function openUserBuff():self
    {
        $this->useBuffQuery = true;
        $this->set_mysql_use_buffered_query(true);
        return $this;
    }    
    
    /**
     * 关闭 mysql缓存结果集方式, 处理大数据时用
     *
     * @return void
     */
	public function closeUserBuff():self
    {
        $this->useBuffQuery = false;
        $this->set_mysql_use_buffered_query(false);
        return $this;
    }    


    /**
     * 开启事务
     *
     * @return void
     */
	public function begin():self
	{
        if($this->tranStartMethod==''){
            $this->connect();
            $backtrace = debug_backtrace();
            $backtrace = $backtrace[count($backtrace)-2];
            $this->tranStartMethod = $backtrace['class'].'.'.$backtrace['function'];
            $this->hasTriggerRollBack = false;
            $this->pdo->beginTransaction();
        }
        return $this;
	}

    /**
     * 触发回滚事务标记，等待 end() 方法回滚, 有此标记后面的SQL都不会执行
     *
     * @return void
     */
	public function rollBack():self
	{
        $this->hasTriggerRollBack = true;
        return $this;
    }

    /**
     * 结束事务
     *
     * @return void
     */
	public function end():self
	{
        $backtrace = debug_backtrace();
        $backtrace = $backtrace[count($backtrace)-2];
        $tranEndMethod = $backtrace['class'].'.'.$backtrace['function'];
        if($this->tranStartMethod==''){
            throw new \Exception(slg("没有开启事务,或闭合不成对").$tranEndMethod);
        }else{
            //事务最后提交者 = 事务最先开启者
            if($tranEndMethod == $this->tranStartMethod){
                $this->tranStartMethod = '';
                if($this->hasError == false || $this->hasTriggerRollBack == false){
                    $this->pdo->commit();
                }else{
                    $this->pdo->rollBack();
                    if($this->hasError == false){
                        throw new \Exception(slg("发现 SQL 错误,事务回滚").$tranEndMethod);
                    }
                }
            }
        }
        return $this;
	}

    /**
     * 检测事务是否正常结束
     *
     * @return bool
     */
	public function checkTransaction():bool
	{
        return $this->tranStartMethod == '';
    }

    /**
     * 获取事务最先开启者
     *
     * @return string
     */
	public function getTransactionStartMethod():string
	{
        return $this->tranStartMethod;
    }

    /**
     * 使用指定字段值做为返回结果的键值
     *
     * @param string|array 一个或者多个字段
     * @return self
     */
	public function key($keys):self
	{
        if(is_string($keys)) $keys=[$keys];
        $this->resultSetKeyFields = $keys;
        return $this;
    }
   
    /**
     * 取查询结果所有行
     *
     * @param [type] $type 默认\PDO::FETCH_ASSOC, 可选:\PDO::FETCH_NUM,\PDO::FETCH_BOTH
     * @return array
     */
    public function all($type=\PDO::FETCH_ASSOC):array
    {
        if(empty($this->statement)){
            return [];
        }else{
            if(is_null($this->resultSetKeyFields)){
                return $this->statement->fetchAll($type) ?: [];
            }else{
                $rows = $this->statement->fetchAll($type) ?: [];
                if(!empty($rows)){
                    $result = [];
                    foreach($rows as $k=>$row){
                        $key = '';
                        foreach($this->resultSetKeyFields as $v){
                            $key .= $row[$v].'.';
                        }
                        $key = rtrim($key,'.');
                        $result[$key] = $row;
                    }
                    unset($rows);
                    return $result;
                }else{
                    return $rows;
                }
            }
        }
    }

    /**
     * 取查询结果中的一行
     *
     * 自动加 limit 0,1
     * @param [type] $type 默认\PDO::FETCH_ASSOC, 可选:\PDO::FETCH_NUM,\PDO::FETCH_BOTH
     * @return array
     */
    public function row($type=\PDO::FETCH_ASSOC):array
    {
        if(empty($this->statement)){
            return [];
        }else{
            return $this->statement->fetch($type) ?: [];
        }
    }

    /**
     * 取查询结果中的第一行第一列
     *
     * 自动加 limit 0,1
     * @param [type] $type 默认\PDO::FETCH_ASSOC, 可选:\PDO::FETCH_NUM,\PDO::FETCH_BOTH
     * @return mixed
     */
    public function one()
    {
        if (empty($this->statement)) {
            return null;
        } else {
            return $this->statement->fetchColumn() ?: null;
        }
    }

    /**
     * 返回最后一次插入的自动ID
     *
     * @return integer
     */
    public function insertId():int
    {
        return $this->pdo->lastInsertId();
    }

    /**返回当前查询影响的记录数*/
    public function queryCount():int
    {
        if(empty($this->statement)){
            return 0;
        }else{
            return $this->statement->rowCount();
        }
    }

    /**
     * 取最后一次执行的sql
     *
     * @return string
     */
    public function lastSql():string
    {
		return $this->lastSql;
    }

    /**
     * 获取数据库名
     *
     * @return string
     */
	public function getDatabaseName():string
    {
        return $this->config['database'];
    }

    /**
     * 获取原始 PDO 对象
     *
     * @return \PDO
     */
	public function getPDO():\PDO
    {
        return $this->pdo;
    }

    /**
     * 获取 PDOStatement 原始结果集对象 
     *
     * @return \PDOStatement | false
     */
    public function statement()
    {
		return $this->statement;
    }

    /**
     * 连接数据库
     *
     * @return self
     */
	public function connect():self
    {
        if(is_null($this->pdo)){
            if($this->config['single']){
                if(!isset(\mvc::$dbList[$this->dbkey])){
                    $this->pdo = $this->createPDO();
                    \mvc::$dbList[$this->dbkey] = $this;
                }else{
                    $this->pdo = \mvc::$dbList[$this->dbkey]->getPDO();
                }
            }else{
                $this->pdo = $this->createPDO();
                \mvc::$dbList[$this->dbkey.'.'.guid()] = $this; 
            }
        }
        return $this;
    }

    /**
     * 创建 PDO 并连接数据库
     *
     * @return \PDO
     */
	protected function createPDO():\PDO
    {
        $pdo = null;
        switch($this->config['type'])
        {
            case 'sqlite':
                $dsn = "sqlite:".$this->config['database'];
                try{
                    $pdo = new \PDO($dsn);
                } catch (\PDOException $e) {
                    $this->log($e->getMessage());
                    exit;
               }
                break;
            case 'mysql':
                $dsn = "mysql:host=".$this->config['hostname'].";port=".$this->config['hostport'].";dbname=".$this->config['database'];
                try{
                    $pdo = new \PDO($dsn, $this->config['username'], $this->config['password'],$this->config['params']);
                    if($this->config['charset']<>''){
                        $pdo->exec("SET character_set_connection=" . $this->config['charset'] . ", character_set_results=" . $this->config['charset'] . ", character_set_client=binary");
                    }
                } catch (\PDOException $e) {
                    $this->log($e->getMessage());
                    exit;
                }
                break;
			case 'oci':
                $dsn = "oci:dbname=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$this->config['hostname'].")(PORT=".$this->config['hostport']."))(CONNECT_DATA=(SID=".$this->config['sid'].")))";
                try{
					if($this->config['charset']<>''){
                        $dsn .= ';charset='.$this->config['charset'];
                    }
					$pdo = new \PDO($dsn, $this->config['username'], $this->config['password']);
                } catch (\PDOException $e) {
                    $this->log($e->getMessage());
                    exit;
                }
                break;
			case 'pgsql':
                $dsn = "pgsql:dbname=".$this->config['database'].";host=".$this->config['hostname'].";port=".$this->config['hostport'];
                try{
					$pdo = new \PDO($dsn, $this->config['username'], $this->config['password']);
                } catch (\PDOException $e) {
                    $this->log($e->getMessage());
                    exit;
                }
                break;
        }
        return $pdo;
    }

    /**
     * 日志处理
     *
     * @param string $message
     * @return void
     */
    protected function log(string $message)
    {
        $error = [
            'message' => "[" . __CLASS__ . "] " . $message,
            'sql' => $this->lastSql,
        ];
        print_r($error);
        \mvc::errorMake($error);
    }


}