<?php
/* * *********************************************************
 * [cml] (C)2012 - 3000 cml http://linhecheng.com
 * @Author  linhecheng<linhechengbush@gmail.com>
 * @Date: 13-6-26 上午11:23
 * @version  1.0 
 * cml框架 Mysql Pdb驱动类
 * *********************************************************** */

defined('CML_PATH') || exit();

import('DbBase',CML_LIB_DRIVER_PATH.DIR_SEP.'Db'.DIR_SEP, '.class.php');
class DbPdoMySql extends DbBase
{
	protected  $bindParams = array();//执行sql时绑定的参数

	public function __construct($conf)
	{
		$this->conf = $conf;
		$this->tablepre = $this->conf['MASTER']['TABLEPREFIX'];
		$this->isPdo = true; //标出当前驱动为pdo
	}

	/**
	 * 获取当前db所有表名
	 *
	 * @return array
	 */
	public function getTables()
	{
		$stmt = $this->query('SHOW TABLES;', $this->rlink);
		$tables = array();
		while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
			$tables[] = $row['Tables_in_'.$this->conf['MASTER']['DBNAME']];
		}     
		return $tables;
	}

	/**
	 * 获取表字段
	 *
	 * @param type string $table
	 * @param type mixed $table 表前缀 为null时代表table已经带了前缀
	 * @param type int $filter 0 获取表字段详细信息数组 1获取字段以,号相隔组成的字符串
	 *
	 * @return mixed
	 */
	public function getDbFields($table, $tablepre = null, $filter = 0)
	{
		if($filter == 1 && $GLOBALS['APP_DEBUG']) return '*'; //debug模式时直接返回*
		$table = is_null($tablepre) ? strtolower($table) : $tablepre.strtolower($table);
		$info = F($this->conf['MASTER']['DBNAME'].'.'.$table);
		if(!$info || $GLOBALS['APP_DEBUG']) {
			$stmt = $this->query("SHOW COLUMNS FROM $table");
			while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
				$info[$row['Field']] = array(
					'name'    => $row['Field'],
					'type'    => $row['Type'],
					'notnull' => (bool) ($row['Null'] === ''), // not null is empty, null is yes
					'default' => $row['Default'],
					'primary' => (strtolower($row['Key']) == 'pri'),
					'autoinc' => (strtolower($row['Extra']) == 'auto_increment'),
				);
			}
			F($this->conf['MASTER']['DBNAME'].'.'.$table, $info);
		}          
		if($filter) {
			$info = implode('`,`', array_keys($info));
			$info = '`'.$info.'`';
		}
		return $info;
	}

	/**
	 * 根据key取出数据
	 *
	 * @param string $key get('user-uid-123');
	 * @param bool $and 多个条件之间是否为and  true为and false为or
	 * @return array array('uid'=>123, 'username'=>'abc')
	 *
	 * @return array
	 */
	public function get($key, $and = true)
	{
		list($tablename, $condition) = $this->parseKey($key, $and);
		$tablename = $this->tablepre.$tablename;
		$fields = C('DB_FIELDS_CACHE') ? $this->getDbFields($tablename, null, 1) : '*';
		$stmt = $this->prepare("SELECT {$fields} FROM {$tablename} WHERE {$condition}", $this->rlink);
		$this->execute($stmt, $this->bindParams);
		$return = $stmt->fetchAll(PDO::FETCH_ASSOC);
		return $return;
	}

	/**
	 * 根据key 新增/更新 一条数据
	 *
	 * @param string $key eg 'user-uid-$uid'
	 * @param array $data eg: array('username'=>'admin', 'email'=>'linhechengbush@live.com')
	 * @param bool $and 多个条件之间是否为and  true为and false为or
	 *
	 * @return bool
	 */
	public function set($key, $data, $and = true)
	{
		$tablepre = $this->tablepre;
		list($table, $condition) = $this->parseKey($key, $and, 1);
		$tablename = $tablepre.$table;
		if(is_array($data)) {
			$keyarr = array();
			$arr = explode('-', $key);            
			$len = count($arr);
			for($i = 1; $i < $len; $i += 2) {
				if(isset($arr[$i + 1])) {		
					$t = $arr[$i + 1];
					$keyarr[$arr[$i]] = is_numeric($t) ? intval($t) : $t;
				} else {
					$keyarr[$arr[$i]] = null;
				}
			}
			$data += $keyarr;
			$s = self::arrToCondition($data, $table, $tablepre);
			$stmt = $this->prepare("INSERT INTO {$tablename} SET {$s}", $this->wlink);
			$this->execute($stmt, $this->bindParams);
			return  $stmt->rowCount();
		} else {
			return false;
		}
	}

	/**
	 * 根据key更新一条数据
	 *
	 * @param string $key eg 'user-uid-$uid'
	 * @param array $data eg: array('username'=>'admin', 'email'=>'linhechengbush@live.com')
	 * @param bool $and 多个条件之间是否为and  true为and false为or
	 *
	 * @return boolean
	 */
	public function update($key, $data, $and = true)
	{
		$tablepre = $this->tablepre;
		list($tablename, $condition) = $this->parseKey($key, $and, 1, 1);
		$tablename = empty($tablename) ? key($this->_table) : $tablepre.$tablename;
		empty($tablename) && throw_exception(L('_PARSE_SQL_ERROR_NO_TABLE_', null, 'update'));
		$s = self::arrToCondition($data, substr($tablename, strlen($tablepre)), $tablepre);
		$whereCondition = $this->_sql['where'];
		$whereCondition .= empty($condition) ?  '' : (empty($whereCondition) ? 'WHERE ' : '').$condition;
		empty($whereCondition) && throw_exception(L('_PARSE_SQL_ERROR_NO_CONDITION_', null, 'update'));
		$stmt = $this->prepare("UPDATE {$tablename} SET {$s} {$whereCondition}", $this->wlink);
		$this->execute($stmt, $this->bindParams);
		return $stmt->rowCount();
	}

	/**
	 * 根据key值删除数据
	 *
	 * @param string $key eg: 'user-uid-$uid'
	 * @param bool $and 多个条件之间是否为and  true为and false为or
	 *
	 * @return boolean
	 */
	public function delete($key, $and = true)
	{
		list($tablename, $condition) = $this->parseKey($key, $and, 1, 1);
		$tablename = empty($tablename) ? key($this->_table) : $this->tablepre.$tablename;
		empty($tablename) && throw_exception(L('_PARSE_SQL_ERROR_NO_TABLE_', null, 'delete'));
		$whereCondition = $this->_sql['where'];
		$whereCondition .= empty($condition) ?  '' : (empty($whereCondition) ? 'WHERE ' : '').$condition;
		empty($whereCondition) && throw_exception(L('_PARSE_SQL_ERROR_NO_CONDITION_', null, 'delete'));
		$stmt = $this->prepare("DELETE FROM {$tablename} {$whereCondition}", $this->wlink);
		$this->execute($stmt, $this->bindParams);
		return $stmt->rowCount();
	}

	/**
	 * 根据表名删除数据
	 *
	 * @param string $tablename
	 *
	 * @return bool
	 */
	public function truncate($tablename)
	{
		$tablename = $this->tablepre.$tablename;		
		$stmt = $this->prepare("TRUNCATE {$tablename}");
		return $stmt->execute();//不存在会报错，但无关紧要
	}

	/**
	 * 获取多条数据
	 *
	 * @return array
	 */
	public function select()
	{
	        $columns = ($this->_sql['columns'] == '*')  ? ( C('DB_FIELDS_CACHE') ? $this->getDbFields(key($this->_table), null, 1) : '*' ) : $this->_sql['columns'];
	        $table = $deper = $joinOn = '';
	        foreach($this->_table as $key => $val) {
			if(isset($this->_join[$key])) {
				$deper = ' INNER JOIN';
			} else if(isset($this->_leftJoin[$key])) {
				$deper = ' LEFT JOIN';
			}  else if(isset($this->_rightJoin[$key])) {
				$deper = ' RIGHT JOIN';
			} else {
				!empty($table) && $deper = ' ,';
			}
			if(is_null($val)) {
				$table .= "{$deper} {$key}";
			} else {
				$table .= "{$deper} {$key} AS {$val}";
			}
	        }

		while($currentOn = current($this->_joinOn)) {
			$joinOn .= empty($joinOn) ? "ON {$currentOn}" : " AND {$currentOn}";
			next($this->_joinOn);
		}
		empty($joinOn) || $joinOn = ' '.$joinOn;
		$sql = "SELECT $columns FROM {$table} {$joinOn} ".$this->_sql['where'].$this->_sql['groupBy'].$this->_sql['having'].$this->_sql['orderBy'].$this->_sql['limit'];
		empty($table) && throw_exception(L('_PARSE_SQL_ERROR_NO_TABLE_', null, 'select'));
		$stmt = $this->prepare($sql, $this->rlink);
		$this->execute($stmt, $this->bindParams);
		return $stmt->fetchAll(PDO::FETCH_ASSOC);
	}

	/**
	 * 获取一条数据
	 *
	 * @param string $sql sql语句
	 *
	 * @return  array
	 */
	public function one($sql, $link = null)
	{
		is_null($link) && $link = $this->rlink;
		$stmt = $this->query($sql, $link);
		return $stmt->fetch(PDO::FETCH_ASSOC); 	
	}

	/**
	 * 统计符合条件的数据有多少条
	 *
	 * @param string $tablename 表名
	 *
	 * @return array
	 */
	public function count($tablename = '')
	{
		$tablename = empty($tablename) ? key($this->_table) : $this->tablepre.$tablename;
		$stmt = $this->prepare("SELECT COUNT(*) AS num FROM {$tablename} ".$this->_sql['where'].$this->_sql['groupBy'].$this->_sql['having'].$this->_sql['orderBy'].$this->_sql['limit'], $this->rlink);
		$this->execute($stmt, $this->bindParams);
		$return = array();
		while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
			$return[] = $row['num'];
		}
		return $return;
	}

	/**
	 * 取出表某列的最大值
	 *
	 * @param string $key eg： user-id
	 *
	 * @return int
	 */
	public function max($key = '')
	{
		empty($key) && list($tablename, $col) = explode('-', $key);
		$tablename = empty($tablename) ? key($this->_table) : $this->tablepre.$tablename;
		$col = empty($col) ? $this->getPk(substr($tablename, strlen($this->tablepre)), $this->tablepre) : $col;
		$stmt = $this->prepare("SELECT MAX({$col}) AS max FROM {$tablename} ".$this->_sql['where'].$this->_sql['groupBy'].$this->_sql['having'].$this->_sql['orderBy'].$this->_sql['limit'],$this->rlink);
		$this->execute($stmt, $this->bindParams);
		while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
			$return[] = $row['max'];
		}
	}

	/**
	 * 取出表某列的最小值
	 *
	 * @param string $key eg： user-id
	 *
	 * @return int
	 */
	public function min($key = '')
	{
		empty($key) && list($tablename, $col) = explode('-', $key);
		$tablename = empty($tablename) ? key($this->_table) : $this->tablepre.$tablename;
		$col = empty($col) ? $this->getPk(substr($tablename, strlen($this->tablepre)), $this->tablepre) : $col;
		$stmt = $this->prepare("SELECT MIN({$col}) AS min FROM {$tablename} ".$this->_sql['where'].$this->_sql['groupBy'].$this->_sql['having'].$this->_sql['orderBy'].$this->_sql['limit'],$this->rlink);
		$this->execute($stmt, $this->bindParams);
		while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
			$return[] = $row['min'];
		}
	}

	/**
	 * 执行sql语句并返回PDOStatement object 适用于select
	 *
	 * @param string $sql
	 * @param PDO $link
	 *
	 * @return object
	 */
	public function query($sql, $link = null)
	{
		is_null($link) && $link = $this->wlink;
		$stmt = $link->query($sql);
		//$this->reset(); 执行非orm sql不用reset
		if(!$stmt) {
			$errorInfo = $link->errorInfo();
			throw_exception(L('_DB_QUERY_ERROR_')." [{$sql}] ".$errorInfo[2]);
		}
		$GLOBALS['APP_DEBUG'] &&  CmlDebug::addTipInfo($sql, 2);
		return  $stmt;
	}

	/**
	 * 执行一条sql并返回受影响的行数 适用于insert|update|delete
	 *
	 * @param string $sql
	 * @param PDO $link
	 *
	 * @return int|bool
	 */
	public function exec($sql, $link = null)
	{
		is_null($link) && $link = $this->wlink;
		$result =  $link->exec($sql);
		$this->reset();
		if($result === 0) return true; //此时sql为执行成功，只是影响的行数是0而已
		return $result;
	}

	/**
	 * 获取query执行的结果
	 *
	 * @param $handle  PDOStatement
	 *
	 * @return array
	 */
	public function getQuery($handle)
	{
		return $handle->fetchAll(PDO::FETCH_ASSOC);
	}

	/**
	 * 返回select语句返回结果集中行的数目
	 *
	 * @param $handle  PDOStatement
	 *
	 *@return int
	 */
	public function numRows($handle)
	{
		$rows = $handle->fetchAll(PDO::FETCH_COLUMN);
		return count($rows);
	}

	/**
	 * 返回INSERT，UPDATE 或 DELETE 查询所影响的记录行数。
	 *
	 * @param $handle PDOStatement
	 */
	public function affectedRows($handle)
	{
		return $handle->rowCount();
	}



	/**
	 * 返回结果集中一个字段的值 兼容原生sql
	 *
	 * @param resource $result 结果标识符
	 * @param int $row 行号
	 * @param string | int 要获取的字段
	 *
	 */
	public function result($result, $row = 1, $field = 0)
	{
		return false;
	}

	/**
	 * 获取上一INSERT的主键值
	 *
	 * @param PDO $link
	 *
	 * @return int
	 */
	public function insertId($link = null)
	{
		is_null($link) && $link = $this->wlink;
		return $link->lastInsertId();
	}

	/**
	 * Db连接
	 *
	 * @param string $host
	 * @param string $user,
	 * @param string $password
	 * @param string $name
	 * @param string $charset
	 * @param string $engine
	 * @param string $pconnect
	 *
	 * @return PDO
	 */
	public function connect($host, $username, $password, $dbname, $charset = 'utf8', $engine = '', $pconnect = false)
	{
		$link = '';
		try {
			if($pconnect) {
				$link = new PDO("mysql:host=$host;dbname=$dbname", $username, $password, array(
					PDO::ATTR_PERSISTENT => true
				));
			} else {
				$link = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
			}
		} catch (PDOException $e) {
			throw_exception('Pdo Connect Error! Code:'.$e->getCode().',ErrorInfo!:'.$e->getMessage().'<br />');
		}
		$link->exec("SET names $charset");
		if(!empty($engine) && $engine == 'InnoDB') {
			$link->exec('SET innodb_flush_log_at_trx_commit=2');
		}
		return $link;
	}

	/**
	 * 指定字段的值+1
	 *
	 * @param string $key user-id-1
	 * @param int $val
	 * @param string $field 要改变的字段
	 *
	 * @return bool
	 */
	public function increment($key, $val = 1, $field = null)
	{
		list($tablename, $condition) = self::parseKey($key, true);
		if(is_null($field) || empty($tablename) || empty($condition)) return false;
		$val = intval($val);
		$stmt = $this->prepare('UPDATE  `'.$this->tablepre.$tablename."` SET  `{$field}` =  `{$field}` + {$val}  WHERE  $condition");
		$this->execute($stmt, $this->bindParams);
		return $stmt->rowCount();
	}

	/**
	 * 指定字段的值-1
	 *
	 * @param string $key user-id-1
	 * @param int $val
	 * @param string $field 要改变的字段
	 *
	 * @return bool
	 */
	public function decrement($key, $val = 1, $field = null)
	{
		list($tablename, $condition) = self::parseKey($key, true);
		if(is_null($field) || empty($tablename) || empty($condition)) return false;
		$val = intval($val);
		$stmt = $this->prepare('UPDATE  `'.$this->tablepre.$tablename."` SET  `$field` =  `$field` - $val  WHERE  $condition");
		$this->execute($stmt, $this->bindParams);
		return $stmt->rowCount();
	}

	/**
	 * 预处理语句
	 *
	 * @param string $sql
	 * @param PDO $link
	 *
	 * @return PDOStatement
	 */

	public function prepare($sql, $link = null)
	{
		$this->reset();
		is_null($link) && $link = $this->wlink;
		$GLOBALS['APP_DEBUG'] &&  CmlDebug::addTipInfo($sql, 2);
		try{
			return $link->prepare($sql);  
		} catch (PDOException $e) {
			throw_exception('Pdo Prepare Sql error! Code:'.$e->getCode().',ErrorInfo!:'.$e->getMessage().'<br />');
		}
		return false;
	}

	/**
	 * 执行预处理语句
	 *
	 * @param object $stmt PDOStatement
	 * @param array $param
	 *
	 * @return bool
	 */
	public function execute($stmt, $param = array())
	{
		empty($param) && $param = $this->bindParams;
		$this->bindParams = array();
		if(!$stmt->execute($param)) {
			$error = $stmt->errorInfo();
			throw_exception($error[2]);
		}	
		return true;
	}

	/**
	 *析构函数
	 *
	 */
	public function __destruct()
	{
		$this->close();
	}

	/**
	 * 关闭连接
	 *
	 */
	public function close()
	{
		if(!empty($this->wlink)) {
			C('SESSION_USER') || $this->wlink = null; //开启会话自定义保存时，不关闭防止会话保存失败
		}
	}

	/**
	 *获取mysql 版本
	 *
	 *@param PDO $link
	 *
	 *@return string
	 */
	public function version($link = null)
	{
		is_null($link) && $link = $this->wlink;
		return $link->getAttribute(PDO::ATTR_SERVER_VERSION);
	}

	/**
	 * 开启事务
	 *
	 * @return bool
	 */
	public function  startTransAction()
	{
		return $this->wlink->beginTransaction();
	}

	/**
	 * 提交事务
	 *
	 * @return bool
	 */
	public function commit()
	{
		return $this->wlink->commit();
	}

	/**
	 * 回滚事务
	 *
	 * @return bool
	 */
	public function rollBack()
	{
		return $this->wlink->rollBack();
	}
}