<?php
// 数据库基类,只能通过 Db::initialize 实现初始化
abstract class Db {
	private static $getInitial = array();
	// 数据库类型
	protected $dbType;
	// 当前SQL指令
	protected $queryStr = '';
	protected $linkID;
	protected $dbname;
	protected $querynum = 0;
	protected $debug = true;
	// 查询表达式
	protected $selectSql = 'SELECT%DISTINCT% %FIELDS% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%';
	// 本类入口,类中实现子类数据驱动初始化
	public static function getInitial($moduleDir = '') {
		$t = C('', 1, $moduleDir);
		$k = $t['DB_TYPE'] . $t['DB_NAME'] . md5($t['DB_HOST']);
		if (!isset(self :: $getInitial[$k])) {
			$t = C('', 1, $moduleDir);
			$dbType = ucwords(strtolower($t['DB_TYPE']));
			import('Lib/Driver/' . $dbType, 'jxcms');
			self :: $getInitial[$k] = new $dbType(array('host' => $t['DB_HOST'], 'name' => $t['DB_NAME'], 'user' => $t['DB_USER'], 'pwd' => $t['DB_PWD'], 'charset' => $t['DB_CHARSET'], 'pconnect' => $t['DB_PCONNECT']));
			self :: $getInitial[$k] -> dbType = $dbType;
		}
		return self :: $getInitial[$k];
	}

	/**
	 * value分析
	 *
	 * @access protected
	 * @param mixed $value
	 * @return string
	 */
	protected function parseValue($value) {
		if (is_string($value)) $value = "'" . $this -> escapeString($value) . "'";
		elseif (is_array($value)) $value = array_map(array($this, 'parseValue'), $value);
		elseif (is_null($value)) $value = 'null';
		return $value;
	}

	/**
	 * field分析
	 *
	 * @access protected
	 * @param mixed $fields
	 * @return string
	 */
	protected function parseField($fields) {
		if (is_array($fields)) {
			// 完善数组方式传字段名的支持
			// 支持 'field1'=>'field2' 这样的字段别名定义
			$array = array();
			foreach ($fields as $key => $field) {
				if (is_numeric($key)) $array[] = $this -> addSpecialChar($field);
				else $array[] = $this -> addSpecialChar($key) . ' AS ' . $this -> addSpecialChar($field);
			}
			return implode(', ', $array);
		}
		if (!empty($fields) && is_string($fields)) return $this -> addSpecialChar($fields);
		return '*';
	}

	/**
	 * table分析
	 *
	 * @access protected
	 * @param mixed $table
	 * @return string
	 */
	protected function parseTable($tables) {
		if (is_string($tables)) $tables = explode(',', $tables);
		$array = array();
		foreach($tables as $table) {
			if (strstr($table, ' ')) {
				$tmp = explode(' ', $table);
				$tmp[0] = $this -> addSpecialChar($tmp[0]);
				$array[] = implode(' ', $tmp);
			} else $array[] = $this -> addSpecialChar($table);
		}
		return implode(', ', $array);
	}
	private function checkOneWhere($str) {
		$tmp = preg_replace('/(["|\']).*?\\1/s', '', $str);
		$tmp = strtoupper($tmp);
		if (strstr($tmp, ' BETWEEN ')) $tmp = preg_replace('/ AND /', '', $tmp, 1);
		// 不是单个条件直接返回
		if (strstr($tmp, ' AND ') || strstr($tmp, ' OR ') || strstr($tmp, ' XOR ')) return $str;
		// 规范化IN
		$tmp = strtok($str, '"\'');
		if (stristr($tmp, ' IN(')) {
			$tmp = preg_replace('/ IN\(/i', ' IN (', $tmp, 1);
			$str = preg_replace('/ IN\(/i', ' IN (', $str, 1);
		}
		foreach(array('!=', '>=', '<=', '=', '>', '<', ' NOT IN ', ' IN ', ' NOT LIKE ', ' LIKE ', ' BETWEEN ') as $comp) {
			if (stristr($tmp, $comp)) {
				$t = explode($comp, $str, 2);
				$field = $this -> addSpecialChar($t[0]);
				$value = trim($t[1]);
				if (is_string($value)) {
					$first = substr($value, 0, 1);
					if ($first == '"' || $first == '\'') {
						$value = substr($value, 1, -1);
						if (!stristr($comp, ' IN ')) $value = $this -> parseValue($value);
					} else {
						if (!(stristr($comp, ' IN ') || (strpos($field, '.', 1) && strpos($value, '.', 1)))) {
							$value = $this -> parseValue($value);
						}
					}
				}
				return "$field $comp $value";
			}
		}
		return $str;
	}
	/**
	 * where分析
	 *
	 * @access protected
	 * @param mixed $where 两种形式:1.'a=1';2.array('a=1', 'b=2');多条件建议用数组方式
	 * @param mixed $logic 逻辑条件:默认为 AND
	 * @return string
	 */
	protected function parseWhere($where, $logic = '') {
		if (is_array($where)) { // 使用数组条件表达式
			$logic = trim($logic);
			if ($logic) $logic = ' ' . strtoupper($logic) . ' ';
			else $logic = ' AND ';
			$_where = array();
			foreach($where as $key => $str) {
				if (is_string($key)) {
					if (is_numeric($str)) $str = "$key=$str";
					else $str = empty($str)?"$key=''":"$key=$str";
					$_where[] = $this -> checkOneWhere($str);
				} else {
					if (is_array($str)) {
						$tmp = array();
						foreach($str as $key1 => $str1) {
							if ($key1 === 'logic') {
								$logic1 = ' ' . strtoupper($str1) . ' ';
								continue;
							}
							if (is_string($key1)) {
								if (is_numeric($str1)) $str1 = "$key1=$str1";
								else $str1 = empty($str1)?"$key1=''":"$key1=$str1";
								$tmp[] = $this -> checkOneWhere($str1);
							} else {
								$tmp[] = $this -> checkOneWhere($str1);
							}
						}
						if ($tmp) {
							if (empty($logic1)) $logic1 = ' OR ';
							$_where[] = '(' . implode($logic1, $tmp) . ')';
						}
					} else $_where[] = $this -> checkOneWhere($str);
				}
			}
			$where = implode($logic, $_where);
		} elseif (is_string($where)) {
			$where = $this -> checkOneWhere($where);
		}
		return empty($where)?'':' WHERE ' . $where;
	}
	private function checkOneOrder($order) {
		$order = trim($order);
		if (!empty($order)) {
			$tmp = explode(' ', $order);
			$order = strtoupper(end($tmp));
			if (in_array($order, array('ASC', 'DESC'))) $order = $this -> addSpecialChar($tmp[0]) . ' ' . $order;
			else $order = $this -> addSpecialChar($tmp[0]);
		}
		return $order;
	}
	/**
	 * order分析
	 *
	 * @access protected
	 * @param mixed $order 'id' OR 'id desc' OR array('id desc', 'name desc');
	 * @return string
	 */
	protected function parseOrder($order) {
		if (is_string($order)) {
			if (strstr($order, ',')) $order = explode(',', $order);
			else $order = $this -> checkOneOrder($order);
		}
		if (is_array($order)) {
			$array = array();
			foreach ($order as $val) $array[] = $this -> checkOneOrder($val);
			$order = implode(', ', $array);
		}
		return empty($order)?'':' ORDER BY ' . $order;
	}

	/**
	 * 插入记录
	 *
	 * @access public
	 * @param mixed $data 数据
	 * @param array $options 参数表达式
	 * @param boolean $replace 是否replace
	 * @return false | integer
	 */
	public function insert($data, $options = array(), $replace = false) {
		$values = $fields = array();
		foreach ($data as $key => $val) {
			$val = $this -> parseValue($val);
			if (is_scalar($val)) { // 过滤非标量数据
				$values[] = $val;
				$fields[] = $this -> addSpecialChar($key);
			}
		}
		$sql = ($replace?'REPLACE':'INSERT') . ' INTO ' . $this -> parseTable($options['table']) . ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')';
		if (isset($options['lock'])) $sql .= 'Oracle' == $this -> dbType?' FOR UPDATE NOWAIT ':' FOR UPDATE ';
		return $this -> query($sql);
	}
	/**
	 * 更新记录
	 *
	 * @access public
	 * @param mixed $data 数据
	 * @param array $options 表达式
	 * @return false | integer
	 */
	public function update($data, $options) {
		$set = array();
		foreach ($data as $key => $val) {
			if (is_scalar($val)) { // 过滤非标量数据
				if (substr($val, 0, strlen($key)) == $key) {
					$set[] = $this -> addSpecialChar($key) . ' = ' . $this -> addSpecialChar($key) . substr($val, strlen($key));
				} else
					$set[] = $this -> addSpecialChar($key) . ' = ' . $this -> parseValue($val);
			}
		}
		$logic = isset($options['logic'])?$options['logic']:'';
		$sql = 'UPDATE ' . $this -> parseTable($options['table']) . ' SET ' . implode(', ', $set);
		if (isset($options['where'])) $sql .= $this -> parseWhere($options['where'], $logic);
		if (isset($options['order'])) $sql .= $this -> parseOrder($options['order']);
		if (!empty($options['order'])) $sql .= ' LIMIT ' . $options['limit'];
		if (isset($options['lock'])) $sql .= 'Oracle' == $this -> dbType?' FOR UPDATE NOWAIT ':' FOR UPDATE ';
		return $this -> query($sql);
	}

	/**
	 * 删除记录
	 *
	 * @access public
	 * @param array $options 表达式
	 * @return false | integer
	 */
	public function delete($options = array()) {
		$logic = isset($options['logic'])?$options['logic']:'';
		$sql = 'DELETE FROM ' . $this -> parseTable($options['table']);
		if (isset($options['where'])) $sql .= $this -> parseWhere($options['where'], $logic);
		if (isset($options['order'])) $sql .= $this -> parseOrder($options['order']);
		if (!empty($options['limit'])) $sql .= ' LIMIT ' . $options['limit'];
		if (isset($options['lock'])) $sql .= 'Oracle' == $this -> dbType?' FOR UPDATE NOWAIT ':' FOR UPDATE ';
		return $this -> query($sql);
	}
	/**
	 * union分析
	 *
	 * @access protected
	 * @param mixed $union
	 * @return string
	 */
	protected function parseUnion($union) {
		if (empty($union)) return '';
		if (isset($union['_all'])) {
			$str = 'UNION ALL ';
			unset($union['_all']);
		} else {
			$str = 'UNION ';
		}
		foreach ($union as $u) {
			$sql[] = $str . (is_array($u)?$this -> buildSelectSql($u):$u);
		}
		return implode(' ', $sql);
	}
	/**
	 * 生成查询SQL
	 *
	 * @access public
	 * @param array $options 表达式 array('tableName', 'on', 'type')
	 * @return string
	 */
	public function buildSelectSql($options = array()) {
		// join分析
		$joinStr = '';
		if (!empty($options['join'])) {
			if (is_array($options['join'])) {
				foreach ($options['join'] as $join) {
					if (strstr($join[0], ' ')) {
						$tmp = explode(' ', $join[0]);
						$tmp[0] = $this -> addSpecialChar($tmp[0]);
						$join[0] = implode(' ', $tmp);
					} else $join[0] = $this -> addSpecialChar($join[0]);
					$joinStr .= ' ' . strtoupper($join[2]) . ' JOIN ' . $join[0] . ' ON ' . $join[1];
				}
			}
		}
		$logic = isset($options['logic'])?$options['logic']:'';
		$sql = str_replace(
			array('%TABLE%', '%DISTINCT%', '%FIELDS%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%'),
			array($this -> parseTable($options['table']),
				empty($options['distinct'])?'':' DISTINCT ',
				$this -> parseField(isset($options['field'])?$options['field']:'*'),
				$joinStr,
				isset($options['where'])?$this -> parseWhere($options['where'], $logic):'',
				empty($options['group'])?'':' GROUP BY ' . $this -> addSpecialChar($options['group']),
				empty($options['having'])?'':' HAVING ' . $options['having'],
				isset($options['order'])?$this -> parseOrder($options['order']):'',
				empty($options['limit'])?'':' LIMIT ' . $options['limit'],
				isset($options['union'])?$this -> parseUnion($options['union']):''
				), $this -> selectSql);
		if (isset($options['lock'])) $sql .= 'Oracle' == $this -> dbType?' FOR UPDATE NOWAIT ':' FOR UPDATE ';
		return $sql;
	}
	/**
	 * 查找记录
	 *
	 * @access public
	 * @param array $options 表达式
	 * @return array
	 */
	public function select($options = array()) {
		$sql = $this -> buildSelectSql($options);
		$query = $this -> query($sql);
		$r = (empty($options['limit']) || $options['limit'] !== 1)?$this -> fetchArrays($query):$this -> fetchArray($query);
		$this -> freeResult($query);
		return $r;
	}
	/**
	 * 字段和表名添加`
	 * 保证指令中使用关键字不出错 针对mysql
	 *
	 * @access public
	 * @param mixed $value
	 * @return mixed
	 */
	public function addSpecialChar($value) {
		if (0 === strpos($this -> dbType, 'Mysql')) {
			$value = trim($value);
			if (false !== strpos($value, ' ') || false !== strpos($value, ',') || false !== strpos($value, '*') || false !== strpos($value, '(') || false !== strpos($value, '.') || false !== strpos($value, '`')) {
				// 如果包含* 或者 使用了sql方法 则不作处理
			} else $value = '`' . $value . '`';
		}
		return $value;
	}
	/**
	 * 获取最近一次查询的sql语句
	 *
	 * @access public
	 * @return string
	 */
	public function getLastSql() {
		return $this -> queryStr;
	}
}
