Files
framework/library/think/db/Driver.php

2339 lines
71 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\db;
use PDO;
use think\Cache;
use think\Config;
use think\Db;
use think\Debug;
use think\Exception;
use think\exception\DbBindParamException;
use think\exception\PDOException;
use think\Loader;
use think\Log;
abstract class Driver
{
// PDO操作实例
protected $PDOStatement = null;
// 当前操作的数据表名
protected $table = '';
// 当前操作的数据对象名
protected $name = '';
// 当前SQL指令
protected $queryStr = '';
// 最后插入ID
protected $lastInsID = null;
// 返回或者影响记录数
protected $numRows = 0;
// 事务指令数
protected $transTimes = 0;
// 错误信息
protected $error = '';
// 数据库连接ID 支持多个连接
protected $links = [];
// 当前连接ID
protected $linkID = null;
// 查询参数
protected $options = [];
// 监听回调
protected static $event = [];
// 数据库连接参数配置
protected $config = [
// 数据库类型
'type' => '',
// 服务器地址
'hostname' => '',
// 数据库名
'database' => '',
// 用户名
'username' => '',
// 密码
'password' => '',
// 端口
'hostport' => '',
'dsn' => '',
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
'prefix' => '',
// 数据库调试模式
'debug' => false,
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
'deploy' => 0,
// 数据库读写是否分离 主从式有效
'rw_separate' => false,
// 读写分离后 主服务器数量
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// like字段自动替换为%%包裹
'like_fields' => '',
// 是否严格检查字段是否存在
'fields_strict' => true,
];
// 数据库表达式
protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS', 'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL', 'not null' => 'NOT NULL'];
// 查询表达式
protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%LOCK%%COMMENT%';
// PDO连接参数
protected $params = [
PDO::ATTR_CASE => PDO::CASE_LOWER,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
];
// 参数绑定
protected $bind = [];
/**
* 架构函数 读取数据库配置信息
* @access public
* @param array $config 数据库配置数组
*/
public function __construct($config = '')
{
if (!empty($config)) {
$this->config = array_merge($this->config, $config);
if (is_array($this->config['params'])) {
$this->params = $this->config['params'] + $this->params;
}
}
}
/**
* 利用__call方法实现一些特殊的Model方法
* @access public
* @param string $method 方法名称
* @param array $args 调用参数
* @return mixed
*/
public function __call($method, $args)
{
if (strtolower(substr($method, 0, 5)) == 'getby') {
// 根据某个字段获取记录
$field = Loader::parseName(substr($method, 5));
$where[$field] = $args[0];
return $this->where($where)->find();
} elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
// 根据某个字段获取记录的某个值
$name = Loader::parseName(substr($method, 10));
$where[$name] = $args[0];
return $this->where($where)->value($args[1]);
} else {
throw new Exception(__CLASS__ . ':' . $method . ' method not exist');
}
}
/**
* 指定当前数据表
* @access public
*/
public function setTable($table)
{
$this->table = $table;
}
/**
* 连接数据库方法
* @access public
*/
public function connect($config = '', $linkNum = 0, $autoConnection = false)
{
if (!isset($this->links[$linkNum])) {
if (empty($config)) {
$config = $this->config;
}
try {
if (empty($config['dsn'])) {
$config['dsn'] = $this->parseDsn($config);
}
$this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $this->params);
// 记录数据库连接信息
APP_DEBUG && Log::record('[ DB ] CONNECT: ' . $config['dsn'], 'info');
} catch (\PDOException $e) {
if ($autoConnection) {
Log::record($e->getMessage(), 'error');
return $this->connect($autoConnection, $linkNum);
} else {
throw new Exception($e->getMessage());
}
}
}
return $this->links[$linkNum];
}
/**
* 解析pdo连接的dsn信息
* @access public
* @param array $config 连接信息
* @return string
*/
protected function parseDsn($config)
{}
/**
* 释放查询结果
* @access public
*/
public function free()
{
$this->PDOStatement = null;
}
/**
* 获取PDO对象
* @access public
*/
public function getPdo()
{
if (!$this->linkID) {
return false;
} else {
return $this->linkID;
}
}
/**
* 执行查询 返回数据集
* @access public
* @param string $sql sql指令
* @param array $bind 参数绑定
* @param boolean $fetch 不执行只是获取SQL
* @param boolean $master 是否在主服务器读操作
* @param bool $returnPdo 是否返回 PDOStatement 对象
* @return mixed
*/
public function query($sql, $bind = [], $fetch = false, $master = false, $returnPdo = false)
{
$this->initConnect($master);
if (!$this->linkID) {
return false;
}
// 根据参数绑定组装最终的SQL语句
$this->queryStr = $this->getBindSql($sql, $bind);
if ($fetch) {
return $this->queryStr;
}
//释放前次的查询结果
if (!empty($this->PDOStatement)) {
$this->free();
}
Db::$queryTimes++;
try {
// 调试开始
$this->debug(true);
// 预处理
$this->PDOStatement = $this->linkID->prepare($sql);
// 参数绑定
$this->bindValue($bind);
// 执行查询
$result = $this->PDOStatement->execute();
// 调试结束
$this->debug(false);
return $returnPdo ? $this->PDOStatement : $this->getResult();
} catch (\PDOException $e) {
throw new PDOException($e, $this->config, $this->queryStr);
}
}
/**
* 执行语句
* @access public
* @param string $sql sql指令
* @param array $bind 参数绑定
* @param boolean $fetch 不执行只是获取SQL
* @return integer
*/
public function execute($sql, $bind = [], $fetch = false)
{
$this->initConnect(true);
if (!$this->linkID) {
return false;
}
// 根据参数绑定组装最终的SQL语句
$this->queryStr = $this->getBindSql($sql, $bind);
if ($fetch) {
return $this->queryStr;
}
//释放前次的查询结果
if (!empty($this->PDOStatement)) {
$this->free();
}
Db::$executeTimes++;
try {
// 调试开始
$this->debug(true);
// 预处理
$this->PDOStatement = $this->linkID->prepare($sql);
// 参数绑定操作
$this->bindValue($bind);
// 执行语句
$result = $this->PDOStatement->execute();
// 调试结束
$this->debug(false);
$this->numRows = $this->PDOStatement->rowCount();
if (preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $sql)) {
$this->lastInsID = $this->linkID->lastInsertId();
}
return $this->numRows;
} catch (\PDOException $e) {
throw new PDOException($e, $this->config, $this->queryStr);
}
}
/**
* 组装最终的SQL语句 便于调试
* @access public
* @param string $sql 带参数绑定的sql语句
* @param array $bind 参数绑定列表
* @return string
*/
protected function getBindSql($sql, array $bind = [])
{
if ($bind) {
foreach ($bind as $key => $val) {
$val = $this->parseValue(is_array($val) ? $val[0] : $val);
// 判断占位符
$sql = is_numeric($key) ?
substr_replace($sql, $val, strpos($sql, '?'), 1) :
str_replace([':' . $key . ')', ':' . $key . ' '], [$val . ')', $val . ' '], $sql . ' ');
}
}
return $sql;
}
/**
* 参数绑定
* 支持 ['name'=>'value','id'=>123] 对应命名占位符
* 或者 ['value',123] 对应问号占位符
* @access public
* @param array $bind 要绑定的参数列表
* @return void
* @throws \think\Exception
*/
protected function bindValue(array $bind = [])
{
foreach ($bind as $key => $val) {
// 占位符
$param = is_numeric($key) ? $key + 1 : ':' . $key;
if (is_array($val)) {
$result = $this->PDOStatement->bindValue($param, $val[0], $val[1]);
} else {
$result = $this->PDOStatement->bindValue($param, $val);
}
if (!$result) {
throw new DbBindParamException(
"Error occurred when binding parameters '{$param}'",
$this->config,
$this->queryStr,
$bind
);
}
}
}
/**
* 获得数据集
* @access private
* @return array
*/
private function getResult()
{
$result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC);
$this->numRows = count($result);
return $result;
}
/**
* 执行数据库事务
* @access public
* @param callable $callback 数据操作方法回调
* @return void
*/
public function transaction($callback){
$this->startTrans();
try {
if (is_callable($callback)) {
call_user_func_array($callback, [$sql, $runtime, $explain]);
}
$this->commit();
}catch (\PDOException $e) {
$this->rollback()
}
}
/**
* 启动事务
* @access public
* @return void
*/
public function startTrans()
{
$this->initConnect(true);
if (!$this->linkID) {
return false;
}
//数据rollback 支持
if (0 == $this->transTimes) {
$this->linkID->beginTransaction();
}
$this->transTimes++;
return;
}
/**
* 用于非自动提交状态下面的查询提交
* @access public
* @return boolen
*/
public function commit()
{
if ($this->transTimes > 0) {
try {
$this->linkID->commit();
$this->transTimes = 0;
} catch (\PDOException $e) {
throw new PDOException($e, $this->config, $this->queryStr);
}
}
return true;
}
/**
* 事务回滚
* @access public
* @return boolen
*/
public function rollback()
{
if ($this->transTimes > 0) {
try {
$this->linkID->rollback();
$this->transTimes = 0;
} catch (\PDOException $e) {
throw new PDOException($e, $this->config, $this->queryStr);
}
}
return true;
}
/**
* 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名小写
* @access protected
* @param string $sql sql语句
* @return string
*/
protected function parseSqlTable($sql)
{
if (false !== strpos($sql, '__')) {
$prefix = $this->tablePrefix;
$sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {
return $prefix . strtolower($match[1]);
}, $sql);
}
return $sql;
}
/**
* 查询SQL组装 join
* @access public
* @param mixed $join 关联的表名
* @param mixed $condition 条件
* @param string $type JOIN类型
* @return Model
*/
public function join($join, $condition = null, $type = 'INNER')
{
if (empty($condition)) {
if (is_array($join) && is_array($join[0])) {
// 如果为组数则循环调用join
foreach ($join as $key => $value) {
if (is_array($value) && 2 <= count($value)) {
$this->join($value[0], $value[1], isset($value[2]) ? $value[2] : $type);
}
}
}
} else {
$prefix = $this->config['prefix'];
// 传入的表名为数组
if (is_array($join)) {
if (0 !== $key = key($join)) {
// 设置了键名则键名为表名,键值作为表的别名
$table = $key . ' ' . array_shift($join);
} else {
$table = array_shift($join);
}
if (count($join)) {
// 有设置第二个元素则把第二元素作为表前缀
$table = (string) current($join) . $table;
} else {
// 加上默认的表前缀
$table = $prefix . $table;
}
} else {
$join = trim($join);
if (0 === strpos($join, '__')) {
$table = $this->parseSqlTable($join);
} elseif (false === strpos($join, '(') && !empty($prefix) && 0 !== strpos($join, $prefix)) {
// 传入的表名中不带有'('并且不以默认的表前缀开头时加上默认的表前缀
$table = $prefix . $join;
} else {
$table = $join;
}
}
if (is_array($condition)) {
$condition = implode(' AND ', $condition);
}
$this->options['join'][] = strtoupper($type) . ' JOIN ' . $table . ' ON ' . $condition;
}
return $this;
}
/**
* 查询SQL组装 union
* @access public
* @param mixed $union
* @param boolean $all
* @return Model
*/
public function union($union, $all = false)
{
$this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
if (is_array($union)) {
$this->options['union'] = array_merge($this->options['union'], $union);
} else {
$this->options['union'][] = $union;
}
return $this;
}
/**
* 获取数据表信息
* @access public
* @param string $fetch 获取信息类型 包括 fields type bind pk
* @param string $tableName 数据表名 留空自动获取
* @return mixed
*/
public function getTableInfo($tableName = '', $fetch = '')
{
static $_info = [];
if (!$tableName) {
$tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTableName();
}
if (is_array($tableName)) {
$tableName = key($tableName) ?: current($tableName);
}
if (strpos($tableName, ',')) {
// 多表不获取字段信息
return false;
}
$guid = md5($tableName);
if (!isset($_info[$guid])) {
$info = $this->getFields($tableName);
// 字段大小写转换
switch ($this->params[PDO::ATTR_CASE]) {
case PDO::CASE_LOWER:
$info = array_change_key_case($info);
break;
case PDO::CASE_UPPER:
$info = array_change_key_case($info, CASE_UPPER);
break;
case PDO::CASE_NATURAL:
default:
// 不做转换
}
$fields = array_keys($info);
$bind = $type = [];
foreach ($info as $key => $val) {
// 记录字段类型
$type[$key] = $val['type'];
if (preg_match('/(int|double|float|decimal|real|numeric|serial)/is', $val['type'])) {
$bind[$key] = PDO::PARAM_INT;
} elseif (preg_match('/bool/is', $val['type'])) {
$bind[$key] = PDO::PARAM_BOOL;
} else {
$bind[$key] = PDO::PARAM_STR;
}
if (!empty($val['primary'])) {
$pk[] = $key;
}
}
if (isset($pk)) {
// 设置主键
$pk = count($pk) > 1 ? $pk : $pk[0];
} else {
$pk = null;
}
$result = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk];
$_info[$guid] = $result;
}
return $fetch ? $_info[$guid][$fetch] : $_info[$guid];
}
/**
* 指定查询字段 支持字段排除
* @access public
* @param mixed $field
* @param boolean $except 是否排除
* @return Model
*/
public function field($field, $except = false)
{
if (true === $field) {
// 获取全部字段
$fields = $this->getTableInfo('', 'fields');
$field = $fields ?: '*';
} elseif ($except) {
// 字段排除
if (is_string($field)) {
$field = explode(',', $field);
}
$fields = $this->getTableInfo('', 'fields');
$field = $fields ? array_diff($fields, $field) : $field;
}
$this->options['field'] = $field;
return $this;
}
/**
* 指定查询条件
* @access public
* @param mixed $field 查询字段
* @param mixed $op 查询表达式
* @param mixed $condition 查询条件
* @return Db
*/
public function where($field, $op = null, $condition = null)
{
if ($field instanceof Query) {
// 使用查询对象
$this->options['where'] = $field;
return $this;
}
$where = $this->parseWhereExp($field, $op, $condition);
if (!isset($this->options['where']['AND'])) {
$this->options['where']['AND'] = [];
}
$this->options['where']['AND'] = array_merge($this->options['where']['AND'], $where);
return $this;
}
/**
* 指定查询条件
* @access public
* @param mixed $field 查询字段
* @param mixed $op 查询表达式
* @param mixed $condition 查询条件
* @return Db
*/
public function whereOr($field, $op = null, $condition = null)
{
$where = $this->parseWhereExp($field, $op, $condition);
if (!isset($this->options['where']['OR'])) {
$this->options['where']['OR'] = [];
}
$this->options['where']['OR'] = array_merge($this->options['where']['OR'], $where);
return $this;
}
/**
* 分析查询表达式
* @access public
* @param mixed $field 查询字段
* @param mixed $op 查询表达式
* @param mixed $condition 查询条件
* @return Db
*/
protected function parseWhereExp($field, $op, $condition)
{
if ($field instanceof \Closure) {
$where[] = $field;
} elseif (is_null($op) && is_null($condition)) {
if (is_array($field)) {
// 数组批量查询
$where = $field;
} else {
// 字符串查询
$where[] = ['exp', $field];
}
} elseif (is_array($op)) {
$param = func_get_args();
array_shift($param);
$where[$field] = $param;
} elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) {
// null查询
$where[$field] = [$op, ''];
} elseif (is_null($condition)) {
// 字段相等查询
$where[$field] = ['eq', $op];
} else {
$where[$field] = [$op, $condition];
}
return $where;
}
/**
* 指定查询条件
* @access public
* @param mixed $where 条件表达式
* @return Db
*/
public function whereExist($where)
{
$this->options['where']['AND'][] = ['EXISTS', $where];
return $this;
}
/**
* 指定查询条件
* @access public
* @param mixed $where 条件表达式
* @return Model
*/
public function whereOrExist($where)
{
$this->options['where']['OR'][] = ['EXISTS', $where];
return $this;
}
/**
* 指定查询条件
* @access public
* @param mixed $where 条件表达式
* @return Model
*/
public function whereNotExist($where)
{
$this->options['where']['AND'][] = ['NOT EXISTS', $where];
return $this;
}
/**
* 指定查询条件
* @access public
* @param mixed $where 条件表达式
* @return Model
*/
public function whereOrNotExist($where)
{
$this->options['where']['OR'][] = ['NOT EXISTS', $where];
return $this;
}
/**
* 指定查询数量
* @access public
* @param mixed $offset 起始位置
* @param mixed $length 查询数量
* @return Model
*/
public function limit($offset, $length = null)
{
if (is_null($length) && strpos($offset, ',')) {
list($offset, $length) = explode(',', $offset);
}
$this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : '');
return $this;
}
/**
* 指定分页
* @access public
* @param mixed $page 页数
* @param mixed $listRows 每页数量
* @return Model
*/
public function page($page, $listRows = null)
{
if (is_null($listRows) && strpos($page, ',')) {
list($page, $listRows) = explode(',', $page);
}
$this->options['page'] = [intval($page), intval($listRows)];
return $this;
}
/**
* 指定数据表
* @access public
* @param string $table 表名
* @return Model
*/
public function table($table)
{
if (is_array($table)) {
$this->options['table'] = $table;
} elseif (!empty($table)) {
$this->options['table'] = $this->parseSqlTable($table);
}
return $this;
}
/**
* USING支持 用于多表删除
* @access public
* @param mixed $using
* @return Model
*/
public function using($using)
{
if (is_array($using)) {
$this->options['using'] = $using;
} elseif (!empty($using)) {
$this->options['using'] = $this->parseSqlTable($using);
}
return $this;
}
/**
* 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
* @access public
* @param string|array $field 排序字段
* @param string $order 排序
* @return Model
*/
public function order($field, $order = null)
{
if (!empty($field)) {
if (is_string($field)) {
$field = empty($order) ? $field : [$field => $order];
}
$this->options['order'] = $field;
}
return $this;
}
/**
* 指定group查询
* @access public
* @param string $group GROUP
* @return Model
*/
public function group($group)
{
$this->options['group'] = $group;
return $this;
}
/**
* 指定having查询
* @access public
* @param string $having having
* @return Model
*/
public function having($having)
{
$this->options['having'] = $having;
return $this;
}
/**
* 指定查询lock
* @access public
* @param boolean $lock 是否lock
* @return Model
*/
public function lock($lock = false)
{
$this->options['lock'] = $lock;
return $this;
}
/**
* 指定distinct查询
* @access public
* @param string $distinct 是否唯一
* @return Model
*/
public function distinct($distinct)
{
$this->options['distinct'] = $distinct;
return $this;
}
/**
* 指定数据表别名
* @access public
* @param string $alias 数据表别名
* @return Model
*/
public function alias($alias)
{
$this->options['alias'] = $alias;
return $this;
}
/**
* 指定强制索引
* @access public
* @param string $force 索引名称
* @return Model
*/
public function force($force)
{
$this->options['force'] = $force;
return $this;
}
/**
* 查询注释
* @access public
* @param string $comment 注释
* @return Model
*/
public function comment($comment)
{
$this->options['comment'] = $comment;
return $this;
}
/**
* 获取执行的SQL语句
* @access public
* @param boolean $fetch 是否返回sql
* @return Model
*/
public function fetchSql($fetch = true)
{
$this->options['fetch_sql'] = $fetch;
return $this;
}
/**
* 不主动获取数据集
* @access public
* @param bool $pdo 是否返回 PDOStatement 对象
* @return Model
*/
public function fetchPdo($pdo = true)
{
$this->options['fetch_pdo'] = $pdo;
return $this;
}
/**
* 设置从主服务器读取数据
* @access public
* @return Model
*/
public function master()
{
$this->options['master'] = true;
return $this;
}
/**
* 指定当前模型
* @access public
* @param string $model 模型类名称
* @return object
*/
public function model($model)
{
$this->options['model'] = $model;
return $this;
}
/**
* 参数绑定
* @access public
* @param mixed $key 参数名
* @param mixed $value 绑定变量值
* @param integer $type 绑定类型
* @return Model
*/
public function bind($key, $value = false, $type = PDO::PARAM_STR)
{
if (is_array($key)) {
$this->bind = array_merge($this->bind, $key);
} else {
$this->bind[$key] = [$value, $type];
}
return $this;
}
/**
* 调用命名范围
* @access public
* @param Closure $scope 命名范围 闭包定义
* @param mixed $args 参数
* @return Db
*/
public function scope($scope = '', $args = null)
{
if ($scope instanceof \Closure) {
call_user_func_array($scope, [ & $this, $args]);
}
return $this;
}
/**
* 得到某个字段的值
* @access public
* @param string $field 字段名
* @return mixed
*/
public function value($field)
{
// 返回数据个数
$pdo = $this->field($field)->fetchPdo(true)->find();
return $pdo->fetchColumn();
}
/**
* 得到某个列的数组
* @access public
* @param string $field 字段名 多个字段用逗号分隔
* @param string $key 索引
* @return array
*/
public function column($field, $key = '')
{
$key = $key ? $key . ',' : '';
$pdo = $this->field($key . $field)->fetchPdo(true)->select();
if (1 == $pdo->columnCount()) {
return $pdo->fetchAll(PDO::FETCH_COLUMN);
}
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$fields = array_keys($result[0]);
$count = count($fields);
$key1 = array_shift($fields);
$key2 = $fields ? array_shift($fields) : '';
foreach ($result as $val) {
if ($count > 2) {
$array[$val[$key1]] = $val;
} elseif (2 == $count) {
$array[$val[$key1]] = $val[$key2];
}
}
return $array;
}
/**
* COUNT查询
* @access public
* @param string $field 字段名
* @return integer
*/
public function count($field = '*')
{
return $this->value('COUNT(' . $field . ') AS tp_count');
}
/**
* SUM查询
* @access public
* @param string $field 字段名
* @return integer
*/
public function sum($field = '*')
{
return $this->value('SUM(' . $field . ') AS tp_sum');
}
/**
* MIN查询
* @access public
* @param string $field 字段名
* @return integer
*/
public function min($field = '*')
{
return $this->value('MIN(' . $field . ') AS tp_min');
}
/**
* MAX查询
* @access public
* @param string $field 字段名
* @return integer
*/
public function max($field = '*')
{
return $this->value('MAX(' . $field . ') AS tp_max');
}
/**
* AVG查询
* @access public
* @param string $field 字段名
* @return integer
*/
public function avg($field = '*')
{
return $this->value('AVG(' . $field . ') AS tp_avg');
}
/**
* 设置记录的某个字段值
* 支持使用数据库字段和方法
* @access public
* @param string|array $field 字段名
* @param string $value 字段值
* @return boolean
*/
public function setField($field, $value = '')
{
if (is_array($field)) {
$data = $field;
} else {
$data[$field] = $value;
}
return $this->save($data);
}
/**
* 字段值(延迟)增长
* @access public
* @param string $field 字段名
* @param integer $step 增长值
* @param integer $lazyTime 延时时间(s)
* @return boolean
* @throws \think\Exception
*/
public function setInc($field, $step = 1, $lazyTime = 0)
{
$condition = !empty($this->options['where']) ? $this->options['where'] : [];
if (empty($condition)) {
// 没有条件不做任何更新
throw new Exception('no data to update');
}
if ($lazyTime > 0) {
// 延迟写入
$guid = md5($this->name . '_' . $field . '_' . serialize($condition));
$step = $this->lazyWrite($guid, $step, $lazyTime);
if (empty($step)) {
return true; // 等待下次写入
}
}
return $this->setField($field, ['exp', $field . '+' . $step]);
}
/**
* 字段值(延迟)减少
* @access public
* @param string $field 字段名
* @param integer $step 减少值
* @param integer $lazyTime 延时时间(s)
* @return boolean
* @throws \think\Exception
*/
public function setDec($field, $step = 1, $lazyTime = 0)
{
$condition = !empty($this->options['where']) ? $this->options['where'] : [];
if (empty($condition)) {
// 没有条件不做任何更新
throw new Exception('no data to update');
}
if ($lazyTime > 0) {
// 延迟写入
$guid = md5($this->name . '_' . $field . '_' . serialize($condition));
$step = $this->lazyWrite($guid, -$step, $lazyTime);
if (empty($step)) {
return true; // 等待下次写入
}
}
return $this->setField($field, ['exp', $field . '-' . $step]);
}
/**
* 延时更新检查 返回false表示需要延时
* 否则返回实际写入的数值
* @access public
* @param string $guid 写入标识
* @param integer $step 写入步进值
* @param integer $lazyTime 延时时间(s)
* @return false|integer
*/
protected function lazyWrite($guid, $step, $lazyTime)
{
if (false !== ($value = Cache::get($guid))) {
// 存在缓存写入数据
if (NOW_TIME > Cache::get($guid . '_time') + $lazyTime) {
// 延时更新时间到了,删除缓存数据 并实际写入数据库
Cache::rm($guid);
Cache::rm($guid . '_time');
return $value + $step;
} else {
// 追加数据到缓存
Cache::set($guid, $value + $step, 0);
return false;
}
} else {
// 没有缓存数据
Cache::set($guid, $step, 0);
// 计时开始
Cache::set($guid . '_time', NOW_TIME, 0);
return false;
}
}
/**
* 得到完整的数据表名
* @access protected
* @return string
*/
protected function getTableName()
{
if (!$this->table) {
$tableName = $this->config['prefix'];
$tableName .= Loader::parseName($this->name);
} else {
$tableName = $this->table;
}
return $tableName;
}
/**
* 设置当前name
* @access public
* @param string $name
* @return Db
*/
public function name($name)
{
$this->name = $name;
return $this;
}
/**
* 得到完整的数据表名
* @access public
* @param array $options 表达式参数
* @return string
*/
public function options(array $options)
{
$this->options = $options;
return $this;
}
/**
* 分析表达式(可用于查询或者写入操作)
* @access protected
* @param array $options 表达式参数
* @return array
*/
private function _parseOptions()
{
$options = $this->options;
// 获取数据表
if (empty($options['table'])) {
$options['table'] = $this->getTableName();
}
// 获取字段信息
$fields = $this->getTableInfo($options['table'], 'fields');
// 字段类型检查
if (isset($options['where']) && is_array($options['where']) && !empty($fields)) {
// 对数组查询条件进行字段类型检查
if (isset($options['where']['AND'])) {
foreach ($options['where']['AND'] as $key => $val) {
$key = trim($key);
if (in_array($key, $fields, true) && is_scalar($val) && empty($this->bind[$key])) {
$this->parseTypeBind($options['where']['AND'], $key, $options['table']);
}
}
}
if (isset($options['where']['OR'])) {
foreach ($options['where']['OR'] as $key => $val) {
$key = trim($key);
if (in_array($key, $fields, true) && is_scalar($val) && empty($this->bind[$key])) {
$this->parseTypeBind($options['where']['OR'], $key, $options['table']);
}
}
}
}
// 表别名
if (!empty($options['alias'])) {
$options['table'] .= ' ' . $options['alias'];
}
// 查询过后清空sql表达式组装 避免影响下次查询
$this->options = [];
return $options;
}
/**
* 数据字段自动类型绑定
* @access protected
* @param array $data 数据
* @param string $key 字段名
* @param string $tableName 表名
* @return void
*/
protected function parseTypeBind(&$data, $key, $tableName = '')
{
if (':' == substr($data[$key], 0, 1) && isset($this->bind[substr($data[$key], 1)])) {
// 已经绑定 无需再次绑定 请确保bind方法优先执行
return;
}
$binds = $this->getTableInfo($tableName, 'bind');
$this->bind[$key] = [$data[$key], isset($binds[$key]) ? $binds[$key] : PDO::PARAM_STR];
$data[$key] = ':' . $key;
}
/**
* 获得查询次数
* @access public
* @param boolean $execute 是否包含所有查询
* @return integer
*/
public function getQueryTimes($execute = false)
{
return $execute ? Db::$queryTimes + Db::$executeTimes : Db::$queryTimes;
}
/**
* 获得执行次数
* @access public
* @return integer
*/
public function getExecuteTimes()
{
return Db::$executeTimes;
}
/**
* 关闭数据库
* @access public
*/
public function close()
{
$this->linkID = null;
}
/**
* 设置锁机制
* @access protected
* @return string
*/
protected function parseLock($lock = false)
{
return $lock ? ' FOR UPDATE ' : '';
}
/**
* 数据分析
* @access protected
* @param array $data 数据
* @param array $bind 参数绑定类型
* @param string $type insert update
* @return array
*/
protected function parseData($data, $bind)
{
if (empty($data)) {
return [];
}
$fields = array_keys($bind);
$result = [];
foreach ($data as $key => $val) {
if (!in_array($key, $fields, true)) {
if ($this->config['fields_strict']) {
throw new Exception(' fields not exists :[' . $key . ']');
}
} else {
$item = $this->parseKey($key);
if (isset($val[0]) && 'exp' == $val[0]) {
$result[$item] = $val[1];
} elseif (is_null($val)) {
$result[$item] = 'NULL';
} elseif (is_scalar($val)) {
// 过滤非标量数据
$this->parseTypeBind($data, $key);
$result[$item] = $data[$key];
}
}
}
return $result;
}
/**
* 字段名分析
* @access protected
* @param string $key
* @return string
*/
protected function parseKey($key)
{
return $key;
}
/**
* value分析
* @access protected
* @param mixed $value
* @return string
*/
protected function parseValue($value)
{
if (is_string($value)) {
$value = strpos($value, ':') === 0 && isset($this->bind[substr($value, 1)]) ? $value : $this->quote($value);
} elseif (isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == 'exp') {
$value = $this->quote($value[1]);
} elseif (is_array($value)) {
$value = array_map([$this, 'parseValue'], $value);
} elseif (is_bool($value)) {
$value = $value ? '1' : '0';
} elseif (is_null($value)) {
$value = 'null';
}
return $value;
}
/**
* field分析
* @access protected
* @param mixed $fields
* @return string
*/
protected function parseField($fields)
{
if (is_string($fields) && strpos($fields, ',')) {
$fields = explode(',', $fields);
}
if (is_array($fields)) {
// 支持 'field1'=>'field2' 这样的字段别名定义
$array = [];
foreach ($fields as $key => $field) {
if (!is_numeric($key)) {
$array[] = $this->parseKey($key) . ' AS ' . $this->parseKey($field);
} else {
$array[] = $this->parseKey($field);
}
}
$fieldsStr = implode(',', $array);
} elseif (is_string($fields) && !empty($fields)) {
$fieldsStr = $this->parseKey($fields);
} else {
$fieldsStr = '*';
}
//TODO 如果是查询全部字段并且是join的方式那么就把要查的表加个别名以免字段被覆盖
return $fieldsStr;
}
/**
* table分析
* @access protected
* @param mixed $table
* @return string
*/
protected function parseTable($tables)
{
if (is_array($tables)) {
// 支持别名定义
foreach ($tables as $table => $alias) {
$array[] = !is_numeric($table) ?
$this->parseKey($table) . ' ' . $this->parseKey($alias) :
$this->parseKey($alias);
}
$tables = $array;
} elseif (is_string($tables)) {
$tables = array_map([$this, 'parseKey'], explode(',', $tables));
}
return implode(',', $tables);
}
/**
* where分析
* @access protected
* @param mixed $where
* @return string
*/
protected function parseWhere($where)
{
$whereStr = $this->buildWhere($where);
return empty($whereStr) ? '' : ' WHERE ' . $whereStr;
}
/**
* 生成查询条件SQL
* @access public
* @param mixed $where
* @return string
*/
public function buildWhere($where = [])
{
if (empty($where) && isset($this->options['where'])) {
$where = $this->options['where'];
} elseif (empty($where)) {
$where = [];
}
if ($where instanceof Query) {
// 使用查询对象
return $where->buildWhere();
}
$whereStr = '';
foreach ($where as $key => $val) {
$str = [];
foreach ($val as $field => $value) {
if ($value instanceof \Closure) {
// 使用闭包查询
$class = clone $this;
$class->options([]);
call_user_func_array($value, [ & $class]);
$str[] = ' ' . $key . ' ( ' . $class->buildWhere() . ' )';
} else {
if (strpos($field, '|')) {
// 不同字段使用相同查询条件OR
$array = explode('|', $field);
$item = [];
foreach ($array as $k) {
$item[] = $this->parseWhereItem($k, $value);
}
$str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )';
} elseif (strpos($field, '&')) {
// 不同字段使用相同查询条件AND
$array = explode('&', $field);
$item = [];
foreach ($array as $k) {
$item[] = $this->parseWhereItem($k, $value);
}
$str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )';
} else {
// 对字段使用表达式查询
$field = is_string($field) ? $field : '';
$str[] = ' ' . $key . ' ' . $this->parseWhereItem($field, $value, $key);
}
}
}
$whereStr .= empty($whereStr) ? substr(implode('', $str), strlen($key) + 1) : implode('', $str);
}
return $whereStr;
}
// where子单元分析
protected function parseWhereItem($key, $val, $rule = '')
{
if ($key) {
// 字段分析
$key = $this->parseKey($key);
}
// 查询规则和条件
if (!is_array($val)) {
$val = ['=', $val];
}
list($exp, $value) = $val;
// 对一个字段使用多个查询条件
if (is_array($exp)) {
foreach ($val as $item) {
$str[] = $this->parseWhereItem($key, $item);
}
return '( ' . implode(' ' . $rule . ' ', $str) . ' )';
}
// 检测操作符
if (!in_array($exp, $this->exp)) {
$exp = strtolower($exp);
if (isset($this->exp[$exp])) {
$exp = $this->exp[$exp];
} else {
throw new Exception('where express error:' . $exp);
}
}
$whereStr = '';
if (in_array($exp, ['=', '<>', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE'])) {
// 比较运算 及 模糊匹配
$whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value);
} elseif ('EXP' == $exp) {
// 表达式查询
$whereStr .= $key . ' ' . $value;
} elseif (in_array($exp, ['NOT NULL', 'NULL'])) {
// NULL 查询
$whereStr .= $key . ' IS ' . $exp;
} elseif (in_array($exp, ['NOT IN', 'IN'])) {
// IN 查询
if ($value instanceof \Closure) {
$whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value);
} else {
$value = is_string($value) ? explode(',', $value) : $value;
$zone = implode(',', $this->parseValue($value));
$whereStr .= $key . ' ' . $exp . ' (' . $zone . ')';
}
} elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) {
// BETWEEN 查询
$data = is_string($value) ? explode(',', $value) : $value;
$whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($data[0]) . ' AND ' . $this->parseValue($data[1]);
} elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) {
// EXISTS 查询
$whereStr .= $exp . ' ' . $this->parseClosure($value);
}
return $whereStr;
}
// 执行闭包子查询
protected function parseClosure($call, $show = true)
{
$class = clone $this;
$class->options([]);
call_user_func_array($call, [ & $class]);
return $class->buildSql($show);
}
/**
* limit分析
* @access protected
* @param mixed $lmit
* @return string
*/
protected function parseLimit($limit)
{
return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : '';
}
/**
* join分析
* @access protected
* @param mixed $join
* @return string
*/
protected function parseJoin($join)
{
$joinStr = '';
if (!empty($join)) {
$joinStr = ' ' . implode(' ', $join) . ' ';
}
return $joinStr;
}
/**
* order分析
* @access protected
* @param mixed $order
* @return string
*/
protected function parseOrder($order)
{
if (is_array($order)) {
$array = [];
foreach ($order as $key => $val) {
if (is_numeric($key)) {
if (false === strpos($val, '(')) {
$array[] = $this->parseKey($val);
} elseif ('[rand]' == $val) {
$array[] = $this->parseRand();
}
} else {
$sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : '';
$array[] = $this->parseKey($key) . ' ' . $sort;
}
}
$order = implode(',', $array);
}
return !empty($order) ? ' ORDER BY ' . $order : '';
}
/**
* group分析
* @access protected
* @param mixed $group
* @return string
*/
protected function parseGroup($group)
{
return !empty($group) ? ' GROUP BY ' . $group : '';
}
/**
* having分析
* @access protected
* @param string $having
* @return string
*/
protected function parseHaving($having)
{
return !empty($having) ? ' HAVING ' . $having : '';
}
/**
* comment分析
* @access protected
* @param string $comment
* @return string
*/
protected function parseComment($comment)
{
return !empty($comment) ? ' /* ' . $comment . ' */' : '';
}
/**
* distinct分析
* @access protected
* @param mixed $distinct
* @return string
*/
protected function parseDistinct($distinct)
{
return !empty($distinct) ? ' DISTINCT ' : '';
}
/**
* union分析
* @access protected
* @param mixed $union
* @return string
*/
protected function parseUnion($union)
{
if (empty($union)) {
return '';
}
$type = $union['type'];
unset($union['type']);
foreach ($union as $u) {
if ($u instanceof \Closure) {
$sql[] = $type . ' ' . $this->parseClosure($u, false);
} elseif (is_string($u)) {
$sql[] = $type . ' ' . $this->parseSqlTable($u);
}
}
return implode(' ', $sql);
}
/**
* index分析可在操作链中指定需要强制使用的索引
* @access protected
* @param mixed $index
* @return string
*/
protected function parseForce($index)
{
if (empty($index)) {
return '';
}
if (is_array($index)) {
$index = join(",", $index);
}
return sprintf(" FORCE INDEX ( %s ) ", $index);
}
/**
* 获取参数绑定信息并清空
* @access protected
* @param bool $reset 获取后清空
* @return array
*/
protected function getBindParams()
{
$bind = $this->bind;
$this->bind = [];
return $bind;
}
/**
* 插入记录
* @access public
* @param mixed $data 数据
* @param boolean $replace 是否replace
* @return integer
*/
public function insert(array $data, $replace = false)
{
$options = $this->_parseOptions();
$bind = $this->getTableInfo($options['table'], 'bind');
$data = $this->parseData($data, $bind);
if (empty($data)) {
return 0;
}
$fields = array_keys($data);
$values = array_values($data);
// 兼容数字传入方式
$sql = ($replace ? 'REPLACE' : 'INSERT') . ' INTO ' . $this->parseTable($options['table']) . ' (' . implode(',', $fields) . ') VALUES (' . implode(',', $values) . ')';
$sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
$result = $this->execute($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false);
return $result;
}
/**
* 批量插入记录
* @access public
* @param mixed $dataSet 数据集
* @return integer
*/
public function insertAll(array $dataSet)
{
$options = $this->_parseOptions();
if (!is_array($dataSet[0])) {
return false;
}
$bind = $this->getTableInfo($options['table'], 'bind');
$fields = array_map([$this, 'parseKey'], array_keys($dataSet[0]));
foreach ($dataSet as $data) {
//$data = $this->parseData($data, $bind);
$value = array_values($data);
$values[] = 'SELECT ' . implode(',', $value);
}
$sql = 'INSERT INTO ' . $this->parseTable($options['table']) . ' (' . implode(',', $fields) . ') ' . implode(' UNION ALL ', $values);
$sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
return $this->execute($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false);
}
/**
* 通过Select方式插入记录
* @access public
* @param string $fields 要插入的数据表字段名
* @param string $table 要插入的数据表名
* @param array $option 查询数据参数
* @return integer
*/
public function selectInsert($fields, $table)
{
$options = $this->_parseOptions();
if (is_string($fields)) {
$fields = explode(',', $fields);
}
$fields = array_map([$this, 'parseKey'], $fields);
$sql = 'INSERT INTO ' . $this->parseTable($table) . ' (' . implode(',', $fields) . ') ';
$sql .= $this->buildSelectSql($options);
return $this->execute($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false);
}
/**
* 更新记录
* @access public
* @param mixed $data 数据
* @return integer
*/
public function update(array $data)
{
$options = $this->_parseOptions();
if (empty($options['where'])) {
$pk = $this->getTableInfo($options['table'], 'pk');
// 如果存在主键数据 则自动作为更新条件
if (is_string($pk) && isset($data[$pk])) {
$where[$pk] = $data[$pk];
unset($data[$pk]);
} elseif (is_array($pk)) {
// 增加复合主键支持
foreach ($pk as $field) {
if (isset($data[$field])) {
$where[$field] = $data[$field];
} else {
// 如果缺少复合主键数据则不执行
throw new Exception('miss pk data');
}
unset($data[$field]);
}
}
if (!isset($where)) {
// 如果没有任何更新条件则不执行
throw new Exception('miss update condition');
} else {
$options['where']['AND'] = $where;
}
}
$bind = $this->getTableInfo($options['table'], 'bind');
$table = $this->parseTable($options['table']);
$data = $this->parseData($data, $bind);
if (empty($data)) {
return 0;
}
foreach ($data as $key => $val) {
$set[] = $key . '=' . $val;
}
$sql = 'UPDATE ' . $table . ' SET ' . implode(',', $set);
if (strpos($table, ',')) {
// 多表更新支持JOIN操作
$sql .= $this->parseJoin(!empty($options['join']) ? $options['join'] : '');
}
$sql .= $this->parseWhere(!empty($options['where']) ? $options['where'] : '');
if (!strpos($table, ',')) {
// 单表更新支持order和lmit
$sql .= $this->parseOrder(!empty($options['order']) ? $options['order'] : '')
. $this->parseLimit(!empty($options['limit']) ? $options['limit'] : '');
}
$sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
return $this->execute($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false);
}
/**
* 删除记录
* @access public
* @param array $data 表达式
* @return integer
*/
public function delete($data = [])
{
if (!empty($data)) {
// AR模式分析主键条件
$this->parsePkWhere($data);
}
$options = $this->_parseOptions();
if (empty($options['where'])) {
// 如果条件为空 不进行删除操作 除非设置 1=1
throw new Exception('no data to delete without where');
}
$table = $this->parseTable($options['table']);
$sql = 'DELETE FROM ' . $table;
if (strpos($table, ',')) {
// 多表删除支持USING和JOIN操作
if (!empty($options['using'])) {
$sql .= ' USING ' . $this->parseTable($options['using']) . ' ';
}
$sql .= $this->parseJoin(!empty($options['join']) ? $options['join'] : '');
}
$sql .= $this->parseWhere(!empty($options['where']) ? $options['where'] : '');
if (!strpos($table, ',')) {
// 单表删除支持order和limit
$sql .= $this->parseOrder(!empty($options['order']) ? $options['order'] : '')
. $this->parseLimit(!empty($options['limit']) ? $options['limit'] : '');
}
$sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
return $this->execute($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false);
}
public function buildSql($sub = true)
{
return $sub ? '( ' . $this->select(false) . ' )' : $this->select(false);
}
/**
* 查找记录
* @access public
* @param array $options 表达式
* @return array|string
*/
public function select($data = [])
{
if (false === $data) {
// 用于子查询 不查询只返回SQL
$this->options['fetch_sql'] = true;
} elseif (!empty($data)) {
// AR模式主键条件分析
$this->parsePkWhere($data);
}
$options = $this->_parseOptions();
$sql = $this->buildSelectSql($options);
$resultSet = $this->query($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false, !empty($options['master']) ? true : false, isset($options['fetch_pdo']) ? $options['fetch_pdo'] : false);
if (!empty($resultSet)) {
if (is_string($resultSet)) {
// 返回SQL
return $resultSet;
}
if ($resultSet instanceof \PDOStatement) {
// 返回PDOStatement对象
return $resultSet;
}
// 数据列表读取后的处理
if (!empty($options['model'])) {
foreach ($resultSet as $key => $result) {
if (!empty($options['model'])) {
// 返回模型对象
$result = new $options['model']($result);
$result->isUpdate(true);
// 关联查询
if (!empty($options['relation'])) {
$result->relationQuery($options['relation']);
}
}
$resultSet[$key] = $result;
}
if (!empty($options['with'])) {
// 预载入
$result = new $options['model']();
return $result->eagerly($resultSet, $options['with']);
}
}
}
return $resultSet;
}
/**
* 设置关联查询预载入
* @access public
* @param string $with 关联名称
* @return Db
*/
public function with($with)
{
$this->options['with'] = $with;
return $this;
}
/**
* 设置关联查询
* @access public
* @param string $relation 关联名称
* @return Db
*/
public function relation($relation)
{
$this->options['relation'] = $relation;
return $this;
}
/**
* 把主键值转换为查询条件 支持复合主键
* @access public
* @param mixed $options 表达式参数
* @return void
* @throws \think\Exception
*/
protected function parsePkWhere($data)
{
$pk = $this->getTableInfo('', 'pk');
if (is_string($pk)) {
// 根据主键查询
if (is_array($data)) {
$where[$pk] = ['in', $data];
} else {
$where[$pk] = strpos($data, ',') ? ['IN', $data] : $data;
}
$this->options['where']['AND'] = $where;
} elseif (is_array($pk) && is_array($data) && !empty($data)) {
// 根据复合主键查询
foreach ($pk as $key) {
if (isset($data[$key])) {
$where[$key] = $data[$key];
} else {
throw new Exception('miss complex primary data');
}
}
$this->options['where']['AND'] = $where;
}
return;
}
/**
* 查找单条记录
* @access public
* @param array $options 表达式
* @return mixed
*/
public function find($data = [])
{
if (!empty($data)) {
// AR模式分析主键条件
$this->parsePkWhere($data);
}
$options = $this->_parseOptions();
$options['limit'] = 1;
$sql = $this->buildSelectSql($options);
$result = $this->query($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false, !empty($options['master']) ? true : false, isset($options['fetch_pdo']) ? $options['fetch_pdo'] : false);
// 数据处理
if (!empty($result)) {
if (is_string($result)) {
// 返回SQL
return $result;
}
if ($result instanceof \PDOStatement) {
// 返回PDOStatement对象
return $result;
}
$data = $result[0];
if (!empty($options['model'])) {
// 返回模型对象
$data = new $options['model']($data);
$data->isUpdate(true);
// 关联查询
if (!empty($options['relation'])) {
$data->relationQuery($options['relation']);
}
}
} else {
$data = false;
}
return $data;
}
/**
* 生成查询SQL
* @access public
* @param array $options 表达式
* @return string
*/
public function buildSelectSql($options = [])
{
if (isset($options['page'])) {
// 根据页数计算limit
list($page, $listRows) = $options['page'];
$page = $page > 0 ? $page : 1;
$listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
$offset = $listRows * ($page - 1);
$options['limit'] = $offset . ',' . $listRows;
}
$sql = $this->parseSql($this->selectSql, $options);
return $sql;
}
/**
* 替换SQL语句中表达式
* @access public
* @param array $options 表达式
* @return string
*/
public function parseSql($sql, $options = [])
{
$sql = str_replace(
['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
[
$this->parseTable($options['table']),
$this->parseDistinct(isset($options['distinct']) ? $options['distinct'] : false),
$this->parseField(!empty($options['field']) ? $options['field'] : '*'),
$this->parseJoin(!empty($options['join']) ? $options['join'] : ''),
$this->parseWhere(!empty($options['where']) ? $options['where'] : ''),
$this->parseGroup(!empty($options['group']) ? $options['group'] : ''),
$this->parseHaving(!empty($options['having']) ? $options['having'] : ''),
$this->parseOrder(!empty($options['order']) ? $options['order'] : ''),
$this->parseLimit(!empty($options['limit']) ? $options['limit'] : ''),
$this->parseUnion(!empty($options['union']) ? $options['union'] : ''),
$this->parseLock(isset($options['lock']) ? $options['lock'] : false),
$this->parseComment(!empty($options['comment']) ? $options['comment'] : ''),
$this->parseForce(!empty($options['force']) ? $options['force'] : ''),
], $sql);
return $sql;
}
/**
* 获取最近一次查询的sql语句
* @access public
* @return string
*/
public function getLastSql()
{
return $this->queryStr;
}
/**
* 获取最近插入的ID
* @access public
* @return string
*/
public function getLastInsID()
{
return $this->lastInsID;
}
/**
* 获取最近的错误信息
* @access public
* @return string
*/
public function getError()
{
if ($this->PDOStatement) {
$error = $this->PDOStatement->errorInfo();
$error = $error[1] . ':' . $error[2];
} else {
$error = '';
}
if ('' != $this->queryStr) {
$error .= "\n [ SQL语句 ] : " . $this->queryStr;
}
return $error;
}
/**
* SQL指令安全过滤
* @access public
* @param string $str SQL字符串
* @return string
*/
public function quote($str)
{
$this->initConnect();
return $this->linkID ? $this->linkID->quote($str) : $str;
}
/**
* 数据库调试 记录当前SQL
* @access protected
* @param boolean $start 调试开始标记 true 开始 false 结束
*/
protected function debug($start)
{
if (!empty($this->config['debug'])) {
// 开启数据库调试模式
if ($start) {
Debug::remark('queryStartTime', 'time');
} else {
// 记录操作结束时间
Debug::remark('queryEndTime', 'time');
$runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime');
$log = $this->queryStr . ' [ RunTime:' . $runtime . 's ]';
$result = [];
// SQL性能分析
if (0 === stripos(trim($this->queryStr), 'select')) {
$result = $this->getExplain($this->queryStr);
}
// SQL监听
$this->trigger($this->queryStr, $runtime, $result);
}
}
}
/**
* 监听SQL执行
* @access public
* @param callable $callback 回调方法
* @return void
*/
public function listen($callback)
{
self::$event[] = $callback;
}
/**
* 触发SQL事件
* @access protected
* @param string $sql SQL语句
* @param float $runtime SQL运行时间
* @param mixed $explain SQL分析
* @return bool
*/
protected function trigger($sql, $runtime, $explain = [])
{
if (!empty(self::$event)) {
foreach (self::$event as $callback) {
if (is_callable($callback)) {
call_user_func_array($callback, [$sql, $runtime, $explain]);
}
}
} else {
// 未注册监听则记录到日志中
Log::record('[ SQL ] ' . $this->queryStr . ' [ RunTime:' . $runtime . 's ]', 'sql');
Log::record('[ EXPLAIN : ' . var_export($result, true) . ' ]', 'sql');
}
}
/**
* 初始化数据库连接
* @access protected
* @param boolean $master 主服务器
* @return void
*/
protected function initConnect($master = true)
{
if (!empty($this->config['deploy'])) {
// 采用分布式数据库
$this->linkID = $this->multiConnect($master);
} elseif (!$this->linkID) {
// 默认单数据库
$this->linkID = $this->connect();
}
}
/**
* 连接分布式服务器
* @access protected
* @param boolean $master 主服务器
* @return void
*/
protected function multiConnect($master = false)
{
// 分布式数据库配置解析
$_config['username'] = explode(',', $this->config['username']);
$_config['password'] = explode(',', $this->config['password']);
$_config['hostname'] = explode(',', $this->config['hostname']);
$_config['hostport'] = explode(',', $this->config['hostport']);
$_config['database'] = explode(',', $this->config['database']);
$_config['dsn'] = explode(',', $this->config['dsn']);
$_config['charset'] = explode(',', $this->config['charset']);
$m = floor(mt_rand(0, $this->config['master_num'] - 1));
// 数据库读写是否分离
if ($this->config['rw_separate']) {
// 主从式采用读写分离
if ($master)
// 主服务器写入
{
$r = $m;
} else {
if (is_numeric($this->config['slave_no'])) {
// 指定服务器读
$r = $this->config['slave_no'];
} else {
// 读操作连接从服务器
$r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1)); // 每次随机连接的数据库
}
}
} else {
// 读写操作不区分服务器
$r = floor(mt_rand(0, count($_config['hostname']) - 1)); // 每次随机连接的数据库
}
if ($m != $r) {
$db_master = [
'username' => isset($_config['username'][$m]) ? $_config['username'][$m] : $_config['username'][0],
'password' => isset($_config['password'][$m]) ? $_config['password'][$m] : $_config['password'][0],
'hostname' => isset($_config['hostname'][$m]) ? $_config['hostname'][$m] : $_config['hostname'][0],
'hostport' => isset($_config['hostport'][$m]) ? $_config['hostport'][$m] : $_config['hostport'][0],
'database' => isset($_config['database'][$m]) ? $_config['database'][$m] : $_config['database'][0],
'dsn' => isset($_config['dsn'][$m]) ? $_config['dsn'][$m] : $_config['dsn'][0],
'charset' => isset($_config['charset'][$m]) ? $_config['charset'][$m] : $_config['charset'][0],
];
}
$db_config = [
'username' => isset($_config['username'][$r]) ? $_config['username'][$r] : $_config['username'][0],
'password' => isset($_config['password'][$r]) ? $_config['password'][$r] : $_config['password'][0],
'hostname' => isset($_config['hostname'][$r]) ? $_config['hostname'][$r] : $_config['hostname'][0],
'hostport' => isset($_config['hostport'][$r]) ? $_config['hostport'][$r] : $_config['hostport'][0],
'database' => isset($_config['database'][$r]) ? $_config['database'][$r] : $_config['database'][0],
'dsn' => isset($_config['dsn'][$r]) ? $_config['dsn'][$r] : $_config['dsn'][0],
'charset' => isset($_config['charset'][$r]) ? $_config['charset'][$r] : $_config['charset'][0],
];
return $this->connect($db_config, $r, $r == $m ? false : $db_master);
}
/**
* 析构方法
* @access public
*/
public function __destruct()
{
// 释放查询
if ($this->PDOStatement) {
$this->free();
}
// 关闭连接
$this->close();
}
}