<?php
namespace lib;
/**
* 数据库操作类
* 
* - [继承自 Medoo](https://medoo.in)
* @version 1.0.0
*/
class db extends \three\medoo
{
 	public $transactionError = []; //事务执行过程中的错误信息
 	public $dbname; //当前连接数据库
	/**
	 * 初始化类
	 * @param array $options
	 * @return viod
	 */
	
	public function __construct(array $options = []){
		if(empty($options)) $options = \mvc::$cfg['db']['main'];
		$this->dbname = $options['database_name'];
		$md5_serialize_options_dbkey = parent::__construct($options);
		$this->context_info();
		return $md5_serialize_options_dbkey;
	}

	public function context_info()
	{
		$opUser = getOpuser();
		$this->pdo->exec("SET @context_info = '{$opUser}'");
	}

	public function close()
	{
		unset(\mvc::$dblinks[$this->dbkey]);
	}
	
    public function whereToSql($where)
    {
        $map = [];
        $sql = $this->whereClause($where,$map);
        $haswhere = false;
        if($map){
            foreach($map as $k=>$v){
                $sql = str_replace($k,"'".$v[0]."'",$sql);
            }
            if($sql!='') $sql = ' AND '.ltrim(substr(ltrim($sql),5));
        }
        return $sql;
    }
    
    public function selectContextSql($table,$join,$fields,$where)
    {
		preg_match('/(?<table>[a-zA-Z0-9_]+)\s*\((?<alias>[a-zA-Z0-9_]+)\)/i', $table, $table_match);

		if (isset($table_match[ 'table' ], $table_match[ 'alias' ]))
		{
			$table = $this->tableQuote($table_match[ 'table' ]);
			$table_query = $table . ' AS ' . $this->tableQuote($table_match[ 'alias' ]);
		}
		else
		{
			$table = $this->tableQuote($table);
			$table_query = $table;
		}
        $table_query .= ' '. $this->buildJoin($table, $join);
        $column = $this->columnPush($fields, $map, true, !empty($join));
        return 'SELECT ' . $column . ' FROM ' . $table_query . ' WHERE 1=1'. $this->whereToSql($where);
    }

    public function select($table, $join, $columns = null, $where = null)
    {
        if($join){
            $result = parent::select($table, $join, $columns, $where);
        }else{
            $result = parent::select($table, $columns, $where);
        }
        if(empty($result)){
            $result = [];
        }
        return $result;
    }

    public function cmd($cmd, $table, $join, $columns = null, $where = null)
    {
        if($join){
            $result = parent::{$cmd}($table, $join, $columns, $where);
        }else{
            $result = parent::{$cmd}($table, $columns, $where);
        }
        return $result;
    }
    
	public function hasError(){
		return ($this->errorInfo && ($this->errorInfo[0]!="00000")) || !empty(\mvc::$transactionError);
	}

	public function begin(){
		if(\mvc::$transactionRegi[$this->dbkey] == 0){
    	    $this->pdo = \mvc::$dblinks[$this->dbkey];
    	    $this->ping();
			$this->pdo->beginTransaction();
			\mvc::$transactionError = [];
		}
		\mvc::$transactionRegi[$this->dbkey]++;
	}

	public function rollBack(){
		\mvc::$transactionRegi[$this->dbkey]--;
	    $this->pdo = \mvc::$dblinks[$this->dbkey];
		return $this->pdo->rollBack();
	}

	public function end(){
        $result = true;
		\mvc::$transactionRegi[$this->dbkey]--;
		if(\mvc::$transactionRegi[$this->dbkey]==0){
    	    $this->pdo = \mvc::$dblinks[$this->dbkey];
            if($this->hasError()){
				if(DEBUG){
					dump($this->ping(),\mvc::$transactionError,$this->errorInfo);
				}else{
					errorLog(\mvc::$transactionError,'database');
				}
                $this->pdo->rollBack();
				\mvc::$transactionError = [];
                $result = false;
            }else{
                $this->pdo->commit();
            }
		}
        return $result;
	}

	/**
	 * 替换插入
	 * @param string $table
	 * @param array $data
	 * @param array $keyFields 主键
	 * @param array $noUpdateFields 更新时不参与的字段, 例 create_time
	 * @return string insert|update
	 */
	public function replace($table,$data,$keyFields=null,$noUpdateFields=null){
	    if(is_string($keyFields)) $keyFields = [$keyFields];
	    if(is_string($noUpdateFields)) $noUpdateFields = [$noUpdateFields];
		if(!isset($data[0]) && !is_array($data[0])){
			$data = [$data];
		}
		foreach($data as &$row){
    		if(isset($row['create_time']) && !isset($noUpdateFields['create_time'])){ 
    		    if($noUpdateFields==null)
    		        $noUpdateFields = [];
    		    array_push($noUpdateFields,'create_time');
    		}
			$where = null;
			if($keyFields){
    			foreach($keyFields as $field){
    				$where[$field] = $row[$field];
    			}
			}
			if(!empty($where)){
				if($this->has($table,$where)){
				    if($noUpdateFields){
    					foreach($noUpdateFields as $field){
    						if(isset($row[$field])) 
    							unset($row[$field]);
    					}
				    }
					$this->update($table,$row,$where);
    				return 'update';
				}else{
					$this->insert($table,$row);
    				return 'insert';
				}
			}else{
				$this->insert($table,$row);
				return 'insert';
			}
		}
	}
	
	/**
	 * 是否使用缓存结果集
	 * @param bool $val
	 * @return viod
	 */
	public function useBuff($value){
	    $this->useBuffState = $value;
	}
	/**
	 * 执行原生SQL
	 * @param string $sql
	 * @return pdo statement
	 */
	public function query($query, $map = []){
	    $query = trim($query);
	    $this->querySQL = $query;
		$raw = $this->raw($query, $map);
		$query = $this->buildRaw($raw, $map);
		
		$this->statement = null;
		
		if($this->type == 'mysql'){
		    if(!isset($this->useBuffState)) $this->useBuffState = true;
    		//是否使用mysql缓存结果集方式, 
    		//true  如果使用的话大表大数据处理会存在内存溢出问题,默认使用
    		//false 不使用的话，在循环遍历数据结束前不允许再执行sql查询
		    $this->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $this->useBuffState);
		}
		
		$statement = $this->pdo->prepare($query);
		if (!$statement)
		{
			$this->errorInfo = $this->pdo->errorInfo();
			$this->statement = null;

			return false;
		}
		
		$statement->setFetchMode(\PDO::FETCH_ASSOC);
		
		$this->statement = $statement;
		foreach ($map as $key => $value)
		{
			$statement->bindValue($key, $value[ 0 ], $value[ 1 ]);
		}
		$execute = $statement->execute();
		$this->errorInfo = $statement->errorInfo();
		if (!$execute)
		{
		    $this->doerror();
		}
		return $statement;
	}


	public function insertIgnore($table, $datas)
	{
		$stack = [];
		$columns = [];
		$fields = [];
		$map = [];

		if (!isset($datas[ 0 ]))
		{
			$datas = [$datas];
		}

		foreach ($datas as $data)
		{
			foreach ($data as $key => $value)
			{
				$columns[] = $key;
			}
		}

		$columns = array_unique($columns);

		foreach ($datas as $data)
		{
			$values = [];

			foreach ($columns as $key)
			{
				if ($raw = $this->buildRaw($data[ $key ], $map))
				{
					$values[] = $raw;
					continue;
				}

				$map_key = $this->mapKey();

				$values[] = $map_key;

				if (!isset($data[ $key ]))
				{
					$map[ $map_key ] = [null, PDO::PARAM_NULL];
				}
				else
				{
					$value = $data[ $key ];

					$type = gettype($value);

					switch ($type)
					{
						case 'array':
							$map[ $map_key ] = [
								strpos($key, '[JSON]') === strlen($key) - 6 ?
									json_encode($value) :
									serialize($value),
								PDO::PARAM_STR
							];
							break;

						case 'object':
							$value = serialize($value);

						case 'NULL':
						case 'resource':
						case 'boolean':
						case 'integer':
						case 'double':
						case 'string':
							$map[ $map_key ] = $this->typeMap($value, $type);
							break;
					}
				}
			}

			$stack[] = '(' . implode(', ', $values) . ')';
		}

		foreach ($columns as $key)
		{
			$fields[] = $this->columnQuote(preg_replace("/(\s*\[JSON\]$)/i", '', $key));
		}

		return $this->exec('INSERT IGNORE INTO ' . $this->tableQuote($table) . ' (' . implode(', ', $fields) . ') VALUES ' . implode(', ', $stack), $map);
	}

}