新增
This commit is contained in:
@@ -0,0 +1,960 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db;
|
||||
|
||||
use Closure;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\exception\DbException as Exception;
|
||||
|
||||
/**
|
||||
* Db Base Builder.
|
||||
*/
|
||||
abstract class BaseBuilder
|
||||
{
|
||||
/**
|
||||
* Connection对象
|
||||
*
|
||||
* @var ConnectionInterface
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* 查询表达式映射.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $exp = ['NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME'];
|
||||
|
||||
/**
|
||||
* 查询表达式解析.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $parser = [
|
||||
'parseCompare' => ['=', '<>', '>', '>=', '<', '<='],
|
||||
'parseLike' => ['LIKE', 'NOT LIKE'],
|
||||
'parseBetween' => ['NOT BETWEEN', 'BETWEEN'],
|
||||
'parseIn' => ['NOT IN', 'IN'],
|
||||
'parseExp' => ['EXP'],
|
||||
'parseNull' => ['NOT NULL', 'NULL'],
|
||||
'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'],
|
||||
'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'],
|
||||
'parseExists' => ['NOT EXISTS', 'EXISTS'],
|
||||
'parseColumn' => ['COLUMN'],
|
||||
];
|
||||
|
||||
/**
|
||||
* SELECT SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%';
|
||||
|
||||
/**
|
||||
* INSERT SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
|
||||
|
||||
/**
|
||||
* INSERT ALL SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
|
||||
|
||||
/**
|
||||
* UPDATE SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $updateSql = 'UPDATE%EXTRA% %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
|
||||
|
||||
/**
|
||||
* DELETE SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
|
||||
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param ConnectionInterface $connection 数据库连接对象实例
|
||||
*/
|
||||
public function __construct(ConnectionInterface $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的连接对象实例.
|
||||
*
|
||||
* @return ConnectionInterface
|
||||
*/
|
||||
public function getConnection(): ConnectionInterface
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册查询表达式解析.
|
||||
*
|
||||
* @param string $name 解析方法
|
||||
* @param array $parser 匹配表达式数据
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function bindParser(string $name, array $parser)
|
||||
{
|
||||
$this->parser[$name] = $parser;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $data 数据
|
||||
* @param array $fields 字段信息
|
||||
* @param array $bind 参数绑定
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array;
|
||||
|
||||
/**
|
||||
* 数据绑定处理.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key 字段名
|
||||
* @param mixed $data 数据
|
||||
* @param array $bind 绑定数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string;
|
||||
|
||||
/**
|
||||
* 字段名分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $key 字段名
|
||||
* @param bool $strict 严格检测
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string;
|
||||
|
||||
/**
|
||||
* 查询额外参数分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $extra 额外参数
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseExtra(Query $query, string $extra): string;
|
||||
|
||||
/**
|
||||
* field分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $fields 字段名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseField(Query $query, array $fields): string;
|
||||
|
||||
/**
|
||||
* table分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array|string $tables 表名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseTable(Query $query, array|string $tables): string;
|
||||
|
||||
/**
|
||||
* where分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $where 查询条件
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseWhere(Query $query, array $where): string;
|
||||
|
||||
/**
|
||||
* 生成查询条件SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $where 查询条件
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buildWhere(Query $query, array $where): string
|
||||
{
|
||||
if (empty($where)) {
|
||||
$where = [];
|
||||
}
|
||||
|
||||
$whereStr = '';
|
||||
|
||||
$binds = $query->getFieldsBindType();
|
||||
|
||||
foreach ($where as $logic => $val) {
|
||||
$str = $this->parseWhereLogic($query, $logic, $val, $binds);
|
||||
|
||||
$whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str);
|
||||
}
|
||||
|
||||
return $whereStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 不同字段使用相同查询条件(AND).
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $logic Logic
|
||||
* @param array $val 查询条件
|
||||
* @param array $binds 参数绑定
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseWhereLogic(Query $query, string $logic, array $val, array $binds = []): array
|
||||
{
|
||||
$where = [];
|
||||
foreach ($val as $value) {
|
||||
if ($value instanceof Raw) {
|
||||
$where[] = ' ' . $logic . ' ( ' . $this->parseRaw($query, $value) . ' )';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
if (key($value) !== 0) {
|
||||
throw new Exception('where express error:' . var_export($value, true));
|
||||
}
|
||||
$field = array_shift($value);
|
||||
} elseif (true === $value) {
|
||||
$where[] = ' ' . $logic . ' 1 ';
|
||||
continue;
|
||||
} elseif (!($value instanceof Closure)) {
|
||||
throw new Exception('where express error:' . var_export($value, true));
|
||||
}
|
||||
|
||||
if ($value instanceof Closure) {
|
||||
// 使用闭包查询
|
||||
$whereClosureStr = $this->parseClosureWhere($query, $value, $logic);
|
||||
if ($whereClosureStr) {
|
||||
$where[] = $whereClosureStr;
|
||||
}
|
||||
} elseif (is_array($field)) {
|
||||
$where[] = $this->parseMultiWhereField($query, $value, $field, $logic, $binds);
|
||||
} elseif ($field instanceof Raw) {
|
||||
$where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds);
|
||||
} elseif (str_contains($field, '|')) {
|
||||
$where[] = $this->parseFieldsOr($query, $value, $field, $logic, $binds);
|
||||
} elseif (str_contains($field, '&')) {
|
||||
$where[] = $this->parseFieldsAnd($query, $value, $field, $logic, $binds);
|
||||
} else {
|
||||
// 对字段使用表达式查询
|
||||
$field = is_string($field) ? $field : '';
|
||||
$where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds);
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 不同字段使用相同查询条件(AND).
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $value 查询条件
|
||||
* @param string $field 查询字段
|
||||
* @param string $logic Logic
|
||||
* @param array $binds 参数绑定
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseFieldsAnd(Query $query, $value, string $field, string $logic, array $binds): string
|
||||
{
|
||||
$item = [];
|
||||
|
||||
foreach (explode('&', $field) as $k) {
|
||||
$item[] = $this->parseWhereItem($query, $k, $value, $binds);
|
||||
}
|
||||
|
||||
return ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )';
|
||||
}
|
||||
|
||||
/**
|
||||
* 不同字段使用相同查询条件(OR).
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $value 查询条件
|
||||
* @param string $field 查询字段
|
||||
* @param string $logic Logic
|
||||
* @param array $binds 参数绑定
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseFieldsOr(Query $query, array $value, string $field, string $logic, array $binds): string
|
||||
{
|
||||
$item = [];
|
||||
|
||||
foreach (explode('|', $field) as $k) {
|
||||
$item[] = $this->parseWhereItem($query, $k, $value, $binds);
|
||||
}
|
||||
|
||||
return ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )';
|
||||
}
|
||||
|
||||
/**
|
||||
* 闭包查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param Closure $value 查询条件
|
||||
* @param string $logic Logic
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseClosureWhere(Query $query, Closure $value, string $logic): string
|
||||
{
|
||||
$newQuery = $query->newQuery();
|
||||
$value($newQuery);
|
||||
$whereClosure = $this->buildWhere($newQuery, $newQuery->getOptions('where') ?: []);
|
||||
|
||||
if (!empty($whereClosure)) {
|
||||
$query->bind($newQuery->getBind(false));
|
||||
$where = ' ' . $logic . ' ( ' . $whereClosure . ' )';
|
||||
}
|
||||
|
||||
return $where ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 复合条件查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $value 查询条件
|
||||
* @param mixed $field 查询字段
|
||||
* @param string $logic Logic
|
||||
* @param array $binds 参数绑定
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseMultiWhereField(Query $query, array $value, $field, string $logic, array $binds): string
|
||||
{
|
||||
array_unshift($value, $field);
|
||||
|
||||
$where = [];
|
||||
foreach ($value as $item) {
|
||||
$where[] = $this->parseWhereItem($query, array_shift($item), $item, $binds);
|
||||
}
|
||||
|
||||
return ' ' . $logic . ' ( ' . implode(' AND ', $where) . ' )';
|
||||
}
|
||||
|
||||
/**
|
||||
* where子单元分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $field 查询字段
|
||||
* @param array $val 查询条件
|
||||
* @param array $binds 参数绑定
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseWhereItem(Query $query, $field, array $val, array $binds = []): string;
|
||||
|
||||
/**
|
||||
* 模糊查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param array $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
* @param string $logic
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string;
|
||||
|
||||
/**
|
||||
* 表达式查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param Raw $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string
|
||||
{
|
||||
// 表达式查询
|
||||
return '( ' . $key . ' ' . $this->parseRaw($query, $value) . ' )';
|
||||
}
|
||||
|
||||
/**
|
||||
* 表达式查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param array $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseColumn(Query $query, string $key, $exp, array $value, string $field, int $bindType): string
|
||||
{
|
||||
// 字段比较查询
|
||||
[$op, $field] = $value;
|
||||
|
||||
if (!in_array(trim($op), ['=', '<>', '>', '>=', '<', '<='])) {
|
||||
throw new Exception('where express error:' . var_export($value, true));
|
||||
}
|
||||
|
||||
return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field, true) . ' )';
|
||||
}
|
||||
|
||||
/**
|
||||
* Null查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string;
|
||||
|
||||
/**
|
||||
* 范围查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseBetween(Query $query, string $key, string $exp, array|string $value, $field, int $bindType): string;
|
||||
|
||||
/**
|
||||
* Exists查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param Raw|Closure $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseExists(Query $query, string $key, string $exp, Raw|Closure $value, string $field, int $bindType): string
|
||||
{
|
||||
// EXISTS 查询
|
||||
if ($value instanceof Closure) {
|
||||
$value = $this->parseClosure($query, $value, false);
|
||||
} elseif ($value instanceof Raw) {
|
||||
$value = $this->parseRaw($query, $value);
|
||||
}
|
||||
|
||||
return $exp . ' ( ' . $value . ' )';
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间比较查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string
|
||||
{
|
||||
return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 大小比较查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseCompare(Query $query, string $key, string $exp, $value, $field, int $bindType): string
|
||||
{
|
||||
if (is_array($value)) {
|
||||
throw new Exception('where express error:' . $exp . var_export($value, true));
|
||||
}
|
||||
|
||||
// 比较运算
|
||||
if ($value instanceof Closure) {
|
||||
$value = $this->parseClosure($query, $value);
|
||||
} elseif ($value instanceof Raw) {
|
||||
$value = $this->parseRaw($query, $value);
|
||||
}
|
||||
|
||||
if ('=' == $exp && is_null($value)) {
|
||||
return $key . ' IS NULL';
|
||||
}
|
||||
|
||||
return $key . ' ' . $exp . ' ' . $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间范围查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseBetweenTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$value = explode(',', $value);
|
||||
}
|
||||
|
||||
return $key . ' ' . substr($exp, 0, -4)
|
||||
. $this->parseDateTime($query, $value[0], $field, $bindType)
|
||||
. ' AND '
|
||||
. $this->parseDateTime($query, $value[1], $field, $bindType);
|
||||
}
|
||||
|
||||
/**
|
||||
* IN查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string;
|
||||
|
||||
/**
|
||||
* 闭包子查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param Closure $call
|
||||
* @param bool $show
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseClosure(Query $query, Closure $call, bool $show = true): string
|
||||
{
|
||||
$newQuery = $query->newQuery()->removeOption();
|
||||
$call($newQuery);
|
||||
|
||||
return $newQuery->buildSql($show);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期时间条件解析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $value
|
||||
* @param string $key
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseDateTime(Query $query, $value, string $key, int $bindType): string;
|
||||
|
||||
/**
|
||||
* limit分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $limit
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseLimit(Query $query, string $limit): string;
|
||||
|
||||
/**
|
||||
* join分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $join
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseJoin(Query $query, array $join): string;
|
||||
|
||||
/**
|
||||
* order分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $order
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseOrder(Query $query, array $order): string;
|
||||
|
||||
/**
|
||||
* 分析Raw对象
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param Raw $raw Raw对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseRaw(Query $query, Raw $raw): string;
|
||||
|
||||
/**
|
||||
* 随机排序.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseRand(Query $query): string;
|
||||
|
||||
/**
|
||||
* group分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $group
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseGroup(Query $query, string|array $group): string;
|
||||
|
||||
/**
|
||||
* having分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $having
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseHaving(Query $query, string $having): string;
|
||||
|
||||
/**
|
||||
* comment分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $comment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseComment(Query $query, string $comment): string
|
||||
{
|
||||
if (str_contains($comment, '*/')) {
|
||||
$comment = strstr($comment, '*/', true);
|
||||
}
|
||||
|
||||
return !empty($comment) ? ' /* ' . $comment . ' */' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* distinct分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $distinct
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseDistinct(Query $query, bool $distinct): string;
|
||||
|
||||
/**
|
||||
* union分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $union
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseUnion(Query $query, array $union): string
|
||||
{
|
||||
if (empty($union)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$type = $union['type'];
|
||||
unset($union['type']);
|
||||
|
||||
foreach ($union as $u) {
|
||||
if ($u instanceof Closure) {
|
||||
$sql[] = $type . ' ' . $this->parseClosure($query, $u);
|
||||
} elseif (is_string($u)) {
|
||||
$sql[] = $type . ' ( ' . $u . ' )';
|
||||
}
|
||||
}
|
||||
|
||||
return ' ' . implode(' ', $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* index分析,可在操作链中指定需要强制使用的索引.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $index
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseForce(Query $query, string|array $index): string;
|
||||
|
||||
/**
|
||||
* 设置锁机制.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param bool|string $lock
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseLock(Query $query, bool|string $lock = false): string;
|
||||
|
||||
/**
|
||||
* 生成查询SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param bool $one 是否仅获取一个记录
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function select(Query $query, bool $one = false): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
return str_replace(
|
||||
['%TABLE%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
|
||||
[
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parseDistinct($query, $options['distinct']),
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
$this->parseField($query, $options['field'] ?? []),
|
||||
$this->parseJoin($query, $options['join']),
|
||||
$this->parseWhere($query, $options['where']),
|
||||
$this->parseGroup($query, $options['group']),
|
||||
$this->parseHaving($query, $options['having']),
|
||||
$this->parseOrder($query, $options['order']),
|
||||
$this->parseLimit($query, $one ? '1' : $options['limit']),
|
||||
$this->parseUnion($query, $options['union']),
|
||||
$this->parseLock($query, $options['lock']),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
$this->parseForce($query, $options['force']),
|
||||
],
|
||||
$this->selectSql
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Insert SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function insert(Query $query): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
// 分析并处理数据
|
||||
$data = $this->parseData($query, $options['data']);
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$fields = array_keys($data);
|
||||
$values = array_values($data);
|
||||
|
||||
return str_replace(
|
||||
['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
|
||||
[
|
||||
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
implode(' , ', $fields),
|
||||
implode(' , ', $values),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
],
|
||||
$this->insertSql
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成insertall SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $dataSet 数据集
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function insertAll(Query $query, array $dataSet): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
// 获取绑定信息
|
||||
$bind = $query->getFieldsBindType();
|
||||
|
||||
// 获取合法的字段
|
||||
if (empty($options['field']) || '*' == $options['field']) {
|
||||
$allowFields = array_keys($bind);
|
||||
} else {
|
||||
$allowFields = $options['field'];
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($dataSet as $k => $data) {
|
||||
$data = $this->parseData($query, $data, $allowFields, $bind);
|
||||
|
||||
$values[] = 'SELECT ' . implode(',', array_values($data));
|
||||
|
||||
if (!isset($insertFields)) {
|
||||
$insertFields = array_keys($data);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($insertFields as $field) {
|
||||
$fields[] = $this->parseKey($query, $field);
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
|
||||
[
|
||||
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
implode(' , ', $fields),
|
||||
implode(' UNION ALL ', $values),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
],
|
||||
$this->insertAllSql
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成slect insert SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $fields 数据
|
||||
* @param string $table 数据表
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function selectInsert(Query $query, array $fields, string $table): string
|
||||
{
|
||||
foreach ($fields as &$field) {
|
||||
$field = $this->parseKey($query, $field, true);
|
||||
}
|
||||
|
||||
return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成update SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function update(Query $query): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
$data = $this->parseData($query, $options['data']);
|
||||
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$set = [];
|
||||
foreach ($data as $key => $val) {
|
||||
$set[] = $key . ' = ' . $val;
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
['%TABLE%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
|
||||
[
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
implode(' , ', $set),
|
||||
$this->parseJoin($query, $options['join']),
|
||||
$this->parseWhere($query, $options['where']),
|
||||
$this->parseOrder($query, $options['order']),
|
||||
$this->parseLimit($query, $options['limit']),
|
||||
$this->parseLock($query, $options['lock']),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
],
|
||||
$this->updateSql
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成delete SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function delete(Query $query): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
return str_replace(
|
||||
['%TABLE%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
|
||||
[
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
!empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '',
|
||||
$this->parseJoin($query, $options['join']),
|
||||
$this->parseWhere($query, $options['where']),
|
||||
$this->parseOrder($query, $options['order']),
|
||||
$this->parseLimit($query, $options['limit']),
|
||||
$this->parseLock($query, $options['lock']),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
],
|
||||
$this->deleteSql
|
||||
);
|
||||
}
|
||||
}
|
||||
+1520
File diff suppressed because it is too large
Load Diff
+774
@@ -0,0 +1,774 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db;
|
||||
|
||||
use Closure;
|
||||
use Stringable;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\exception\DbException as Exception;
|
||||
|
||||
/**
|
||||
* Db Builder.
|
||||
*/
|
||||
class Builder extends BaseBuilder
|
||||
{
|
||||
|
||||
/**
|
||||
* 数据分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $data 数据
|
||||
* @param array $fields 字段信息
|
||||
* @param array $bind 参数绑定
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array
|
||||
{
|
||||
if (empty($data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = $query->getOptions();
|
||||
|
||||
// 获取绑定信息
|
||||
if (empty($bind)) {
|
||||
$bind = $query->getFieldsBindType();
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
if (empty($options['field']) || '*' == $options['field']) {
|
||||
$fields = array_keys($bind);
|
||||
} else {
|
||||
$fields = $options['field'];
|
||||
}
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($data as $key => $val) {
|
||||
$item = $this->parseKey($query, $key, true);
|
||||
|
||||
if ($val instanceof Raw) {
|
||||
$result[$item] = $this->parseRaw($query, $val);
|
||||
continue;
|
||||
} elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $query->getFieldType($key))) {
|
||||
$val = json_encode($val);
|
||||
}
|
||||
|
||||
if (str_contains($key, '->')) {
|
||||
[$key, $name] = explode('->', $key, 2);
|
||||
$item = $this->parseKey($query, $key);
|
||||
|
||||
$result[$item . '->' . $name] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key . '->' . $name, $val, $bind) . ')';
|
||||
} elseif (!str_contains($key, '.') && !in_array($key, $fields, true)) {
|
||||
if ($options['strict']) {
|
||||
throw new Exception('fields not exists:[' . $key . ']');
|
||||
}
|
||||
} elseif (is_null($val)) {
|
||||
$result[$item] = 'NULL';
|
||||
} elseif (is_array($val) && !empty($val) && is_string($val[0])) {
|
||||
if (in_array(strtoupper($val[0]), ['INC', 'DEC'])) {
|
||||
$result[$item] = match (strtoupper($val[0])) {
|
||||
'INC' => $item . ' + ' . floatval($val[1]),
|
||||
'DEC' => $item . ' - ' . floatval($val[1]),
|
||||
};
|
||||
}
|
||||
} elseif (is_scalar($val)) {
|
||||
// 过滤非标量数据
|
||||
if (!$query->isAutoBind() && Connection::PARAM_STR == $bind[$key]) {
|
||||
$val = '\'' . $val . '\'';
|
||||
}
|
||||
$result[$item] = !$query->isAutoBind() ? $val : $this->parseDataBind($query, $key, $val, $bind);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据绑定处理.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key 字段名
|
||||
* @param mixed $data 数据
|
||||
* @param array $bind 绑定数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string
|
||||
{
|
||||
if ($data instanceof Raw) {
|
||||
return $this->parseRaw($query, $data);
|
||||
}
|
||||
|
||||
$name = $query->bindValue($data, $bind[$key] ?? Connection::PARAM_STR);
|
||||
|
||||
return ':' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段名分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $key 字段名
|
||||
* @param bool $strict 严格检测
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
|
||||
{
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询额外参数分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $extra 额外参数
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseExtra(Query $query, string $extra): string
|
||||
{
|
||||
return preg_match('/^[\w]+$/i', $extra) ? ' ' . strtoupper($extra) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* field分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $fields 字段名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseField(Query $query, array $fields): string
|
||||
{
|
||||
if (!empty($fields)) {
|
||||
// 支持 'field1'=>'field2' 这样的字段别名定义
|
||||
$array = [];
|
||||
|
||||
foreach ($fields as $key => $field) {
|
||||
if ($field instanceof Raw) {
|
||||
$array[] = $this->parseRaw($query, $field);
|
||||
} elseif (!is_numeric($key)) {
|
||||
$array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true);
|
||||
} else {
|
||||
$array[] = $this->parseKey($query, $field);
|
||||
}
|
||||
}
|
||||
|
||||
$fieldsStr = implode(',', $array);
|
||||
} else {
|
||||
$fieldsStr = '*';
|
||||
}
|
||||
|
||||
return $fieldsStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* table分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array|string $tables 表名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseTable(Query $query, array|string $tables): string
|
||||
{
|
||||
$item = [];
|
||||
$options = $query->getOptions();
|
||||
|
||||
foreach ((array) $tables as $key => $table) {
|
||||
if ($table instanceof Raw) {
|
||||
$item[] = $this->parseRaw($query, $table);
|
||||
} elseif (!is_numeric($key)) {
|
||||
$item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table);
|
||||
} elseif (isset($options['alias'][$table])) {
|
||||
$item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]);
|
||||
} else {
|
||||
$item[] = $this->parseKey($query, $table);
|
||||
}
|
||||
}
|
||||
|
||||
return implode(',', $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* where分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $where 查询条件
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseWhere(Query $query, array $where): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
$whereStr = $this->buildWhere($query, $where);
|
||||
|
||||
if (!empty($options['soft_delete'])) {
|
||||
// 附加软删除条件
|
||||
[$field, $condition] = $options['soft_delete'];
|
||||
|
||||
$binds = $query->getFieldsBindType();
|
||||
$whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : '';
|
||||
$whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, $binds);
|
||||
}
|
||||
|
||||
return empty($whereStr) ? '' : ' WHERE ' . $whereStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* where子单元分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $field 查询字段
|
||||
* @param array $val 查询条件
|
||||
* @param array $binds 参数绑定
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseWhereItem(Query $query, $field, array $val, array $binds = []): string
|
||||
{
|
||||
// 字段分析
|
||||
$key = $field ? $this->parseKey($query, $field, true) : '';
|
||||
|
||||
[$exp, $value] = $val;
|
||||
|
||||
// 检测操作符
|
||||
if (!is_string($exp)) {
|
||||
throw new Exception('where express error:' . var_export($exp, true));
|
||||
}
|
||||
|
||||
$exp = strtoupper($exp);
|
||||
if (isset($this->exp[$exp])) {
|
||||
$exp = $this->exp[$exp];
|
||||
}
|
||||
|
||||
if (is_string($field) && 'LIKE' != $exp) {
|
||||
$bindType = $binds[$field] ?? Connection::PARAM_STR;
|
||||
} else {
|
||||
$bindType = Connection::PARAM_STR;
|
||||
}
|
||||
|
||||
if ($value instanceof Raw) {
|
||||
} elseif ($value instanceof Stringable) {
|
||||
// 对象数据写入
|
||||
$value = $value->__toString();
|
||||
}
|
||||
|
||||
if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && !str_contains($exp, 'TIME')) {
|
||||
if (is_string($value) && str_starts_with($value, ':') && $query->isBind(substr($value, 1))) {
|
||||
} else {
|
||||
$name = $query->bindValue($value, $bindType);
|
||||
$value = ':' . $name;
|
||||
}
|
||||
}
|
||||
|
||||
// 解析查询表达式
|
||||
foreach ($this->parser as $fun => $parse) {
|
||||
if (in_array($exp, $parse)) {
|
||||
return $this->$fun($query, $key, $exp, $value, $field, $bindType, $val[2] ?? 'AND');
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception('where express error:' . $exp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模糊查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param array $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
* @param string $logic
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string
|
||||
{
|
||||
// 模糊匹配
|
||||
if (is_array($value)) {
|
||||
$array = [];
|
||||
foreach ($value as $item) {
|
||||
$name = $query->bindValue($item, Connection::PARAM_STR);
|
||||
$array[] = $key . ' ' . $exp . ' :' . $name;
|
||||
}
|
||||
|
||||
$whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')';
|
||||
} else {
|
||||
$whereStr = $key . ' ' . $exp . ' ' . $value;
|
||||
}
|
||||
|
||||
return $whereStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表达式查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param Raw $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string
|
||||
{
|
||||
// 表达式查询
|
||||
return '( ' . $key . ' ' . $this->parseRaw($query, $value) . ' )';
|
||||
}
|
||||
|
||||
/**
|
||||
* Null查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string
|
||||
{
|
||||
// NULL 查询
|
||||
return $key . ' IS ' . $exp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 范围查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseBetween(Query $query, string $key, string $exp, array|string $value, $field, int $bindType): string
|
||||
{
|
||||
// BETWEEN 查询
|
||||
$data = is_array($value) ? $value : explode(',', $value);
|
||||
|
||||
$min = $query->bindValue($data[0], $bindType);
|
||||
$max = $query->bindValue($data[1], $bindType);
|
||||
|
||||
return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* IN查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string
|
||||
{
|
||||
// IN 查询
|
||||
if ($value instanceof Closure) {
|
||||
$value = $this->parseClosure($query, $value, false);
|
||||
} elseif ($value instanceof Raw) {
|
||||
$value = $this->parseRaw($query, $value);
|
||||
} else {
|
||||
$value = array_unique(is_array($value) ? $value : explode(',', (string) $value));
|
||||
if (count($value) === 0) {
|
||||
return 'IN' == $exp ? '0 = 1' : '1 = 1';
|
||||
}
|
||||
|
||||
if ($query->isAutoBind()) {
|
||||
$array = [];
|
||||
foreach ($value as $v) {
|
||||
$name = $query->bindValue($v, $bindType);
|
||||
$array[] = ':' . $name;
|
||||
}
|
||||
$value = implode(',', $array);
|
||||
} elseif (Connection::PARAM_STR == $bindType) {
|
||||
$value = '\'' . implode('\',\'', $value) . '\'';
|
||||
} else {
|
||||
$value = implode(',', $value);
|
||||
}
|
||||
|
||||
if (!str_contains($value, ',')) {
|
||||
return $key . ('IN' == $exp ? ' = ' : ' <> ') . $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $key . ' ' . $exp . ' (' . $value . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期时间条件解析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $value
|
||||
* @param string $key
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDateTime(Query $query, $value, string $key, int $bindType): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
// 获取时间字段类型
|
||||
if (str_contains($key, '.')) {
|
||||
[$table, $key] = explode('.', $key);
|
||||
|
||||
if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) {
|
||||
$table = $pos;
|
||||
}
|
||||
} else {
|
||||
$table = $options['table'];
|
||||
}
|
||||
|
||||
$type = $query->getFieldType($key);
|
||||
|
||||
if ($type) {
|
||||
if (is_string($value)) {
|
||||
$value = strtotime($value) ?: $value;
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
if (preg_match('/(datetime|timestamp)/is', $type)) {
|
||||
// 日期及时间戳类型
|
||||
$value = date('Y-m-d H:i:s', $value);
|
||||
} elseif (preg_match('/(date)/is', $type)) {
|
||||
// 日期及时间戳类型
|
||||
$value = date('Y-m-d', $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$name = $query->bindValue($value, $bindType);
|
||||
|
||||
return ':' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* limit分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $limit
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLimit(Query $query, string $limit): string
|
||||
{
|
||||
return (!empty($limit) && !str_contains($limit, '(')) ? ' LIMIT ' . $limit . ' ' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* join分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $join
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseJoin(Query $query, array $join): string
|
||||
{
|
||||
$joinStr = '';
|
||||
|
||||
foreach ($join as $item) {
|
||||
[$table, $type, $on] = $item;
|
||||
|
||||
if (str_contains($on, '=')) {
|
||||
[$val1, $val2] = explode('=', $on, 2);
|
||||
|
||||
$condition = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2);
|
||||
} else {
|
||||
$condition = $on;
|
||||
}
|
||||
|
||||
$table = $this->parseTable($query, $table);
|
||||
|
||||
$joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . $condition;
|
||||
}
|
||||
|
||||
return $joinStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* order分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $order
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseOrder(Query $query, array $order): string
|
||||
{
|
||||
$array = [];
|
||||
foreach ($order as $key => $val) {
|
||||
if ($val instanceof Raw) {
|
||||
$array[] = $this->parseRaw($query, $val);
|
||||
} elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) {
|
||||
$array[] = $this->parseOrderField($query, $key, $val);
|
||||
} elseif ('[rand]' == $val) {
|
||||
$array[] = $this->parseRand($query);
|
||||
} elseif (is_string($val)) {
|
||||
if (is_numeric($key)) {
|
||||
[$key, $sort] = explode(' ', str_contains($val, ' ') ? $val : $val . ' ');
|
||||
} else {
|
||||
$sort = $val;
|
||||
}
|
||||
|
||||
if (preg_match('/^[\w\.]+$/', $key)) {
|
||||
$sort = strtoupper($sort);
|
||||
$sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
|
||||
$array[] = $this->parseKey($query, $key, true) . $sort;
|
||||
} else {
|
||||
throw new Exception('order express error:' . $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return empty($array) ? '' : ' ORDER BY ' . implode(',', $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析Raw对象
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param Raw $raw Raw对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRaw(Query $query, Raw $raw): string
|
||||
{
|
||||
$sql = $raw->getValue();
|
||||
$bind = $raw->getBind();
|
||||
|
||||
if ($bind) {
|
||||
$query->bindParams($sql, $bind);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand(Query $query): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* orderField分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param array $val
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseOrderField(Query $query, string $key, array $val): string
|
||||
{
|
||||
if (isset($val['sort'])) {
|
||||
$sort = $val['sort'];
|
||||
unset($val['sort']);
|
||||
} else {
|
||||
$sort = '';
|
||||
}
|
||||
|
||||
$sort = strtoupper($sort);
|
||||
$sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
|
||||
$bind = $query->getFieldsBindType();
|
||||
|
||||
foreach ($val as $k => $item) {
|
||||
$val[$k] = $this->parseDataBind($query, $key, $item, $bind);
|
||||
}
|
||||
|
||||
return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* group分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $group
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseGroup(Query $query, string|array $group): string
|
||||
{
|
||||
if (empty($group)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (is_string($group)) {
|
||||
$group = explode(',', $group);
|
||||
}
|
||||
|
||||
$val = [];
|
||||
foreach ($group as $key) {
|
||||
$val[] = $this->parseKey($query, $key);
|
||||
}
|
||||
|
||||
return ' GROUP BY ' . implode(',', $val);
|
||||
}
|
||||
|
||||
/**
|
||||
* having分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $having
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseHaving(Query $query, string $having): string
|
||||
{
|
||||
return !empty($having) ? ' HAVING ' . $having : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* comment分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $comment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseComment(Query $query, string $comment): string
|
||||
{
|
||||
if (str_contains($comment, '*/')) {
|
||||
$comment = strstr($comment, '*/', true);
|
||||
}
|
||||
|
||||
return !empty($comment) ? ' /* ' . $comment . ' */' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* distinct分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $distinct
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDistinct(Query $query, bool $distinct): string
|
||||
{
|
||||
return !empty($distinct) ? ' DISTINCT ' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* index分析,可在操作链中指定需要强制使用的索引.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $index
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseForce(Query $query, string|array $index): string
|
||||
{
|
||||
if (empty($index)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (is_array($index)) {
|
||||
$index = implode(',', $index);
|
||||
}
|
||||
|
||||
return sprintf(' FORCE INDEX ( %s ) ', $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置锁机制.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param bool|string $lock
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLock(Query $query, bool|string $lock = false): string
|
||||
{
|
||||
if (is_bool($lock)) {
|
||||
return $lock ? ' FOR UPDATE ' : '';
|
||||
}
|
||||
|
||||
if (is_string($lock) && !empty($lock)) {
|
||||
return ' ' . trim($lock) . ' ';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成insertall SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $keys 字段名
|
||||
* @param array $datas 数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function insertAllByKeys(Query $query, array $keys, array $datas): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
// 获取绑定信息
|
||||
$bind = $query->getFieldsBindType();
|
||||
$fields = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($keys as $field) {
|
||||
$fields[] = $this->parseKey($query, $field);
|
||||
}
|
||||
|
||||
foreach ($datas as $k => $data) {
|
||||
foreach ($data as $key => &$val) {
|
||||
if (!$query->isAutoBind()) {
|
||||
$val = Connection::PARAM_STR == $bind[$keys[$key]] ? '\'' . $val . '\'' : $val;
|
||||
} else {
|
||||
$val = $this->parseDataBind($query, $keys[$key], $val, $bind);
|
||||
}
|
||||
}
|
||||
|
||||
$values[] = 'SELECT ' . implode(',', $data);
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
|
||||
[
|
||||
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
implode(' , ', $fields),
|
||||
implode(' UNION ALL ', $values),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
],
|
||||
$this->insertAllSql
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db;
|
||||
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
use think\db\exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* CacheItem实现类.
|
||||
*/
|
||||
class CacheItem
|
||||
{
|
||||
/**
|
||||
* 缓存Key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* 缓存内容.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* 过期时间.
|
||||
*
|
||||
* @var int|DateTimeInterface
|
||||
*/
|
||||
protected $expire;
|
||||
|
||||
/**
|
||||
* 缓存tag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tag;
|
||||
|
||||
/**
|
||||
* 缓存是否命中.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isHit = false;
|
||||
|
||||
public function __construct(string $key = null)
|
||||
{
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为此缓存项设置「键」.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setKey(string $key)
|
||||
{
|
||||
$this->key = $key;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前缓存项的「键」.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前缓存项的有效期
|
||||
*
|
||||
* @return DateTimeInterface|int|null
|
||||
*/
|
||||
public function getExpire()
|
||||
{
|
||||
if ($this->expire instanceof DateTimeInterface) {
|
||||
return $this->expire;
|
||||
}
|
||||
|
||||
return $this->expire ? $this->expire - time() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存Tag.
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 凭借此缓存项的「键」从缓存系统里面取出缓存项.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认缓存项的检查是否命中.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isHit(): bool
|
||||
{
|
||||
return $this->isHit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为此缓存项设置「值」.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function set($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->isHit = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为此缓存项设置所属标签.
|
||||
*
|
||||
* @param string|array $tag
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function tag($tag = null)
|
||||
{
|
||||
$this->tag = $tag;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存项的有效期
|
||||
*
|
||||
* @param mixed $expire
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function expire($expire)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$this->expire = null;
|
||||
} elseif (is_numeric($expire) || $expire instanceof DateInterval) {
|
||||
$this->expiresAfter($expire);
|
||||
} elseif ($expire instanceof DateTimeInterface) {
|
||||
$this->expire = $expire;
|
||||
} else {
|
||||
throw new InvalidArgumentException('not support datetime');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存项的准确过期时间点.
|
||||
*
|
||||
* @param DateTimeInterface $expiration
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function expiresAt(DateTimeInterface $expiration)
|
||||
{
|
||||
$this->expire = $expiration;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存项的过期时间.
|
||||
*
|
||||
* @param int|DateInterval $timeInterval
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function expiresAfter($timeInterval)
|
||||
{
|
||||
if ($timeInterval instanceof DateInterval) {
|
||||
$this->expire = (int) DateTime::createFromFormat('U', (string) time())->add($timeInterval)->format('U');
|
||||
} elseif (is_numeric($timeInterval)) {
|
||||
$this->expire = $timeInterval + time();
|
||||
} else {
|
||||
throw new InvalidArgumentException('not support datetime');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use think\DbManager;
|
||||
|
||||
/**
|
||||
* 数据库连接基础类.
|
||||
*/
|
||||
abstract class Connection implements ConnectionInterface
|
||||
{
|
||||
const PARAM_INT = 1;
|
||||
const PARAM_STR = 2;
|
||||
const PARAM_BOOL = 5;
|
||||
const PARAM_FLOAT = 21;
|
||||
|
||||
/**
|
||||
* 当前SQL指令.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $queryStr = '';
|
||||
|
||||
/**
|
||||
* 返回或者影响记录数.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $numRows = 0;
|
||||
|
||||
/**
|
||||
* 事务指令数.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $transTimes = 0;
|
||||
|
||||
/**
|
||||
* 错误信息.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $error = '';
|
||||
|
||||
/**
|
||||
* 数据库连接ID 支持多个连接.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $links = [];
|
||||
|
||||
/**
|
||||
* 当前连接ID.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $linkID;
|
||||
|
||||
/**
|
||||
* 当前读连接ID.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $linkRead;
|
||||
|
||||
/**
|
||||
* 当前写连接ID.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $linkWrite;
|
||||
|
||||
/**
|
||||
* 数据表信息.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $info = [];
|
||||
|
||||
/**
|
||||
* 查询开始时间.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
protected $queryStartTime;
|
||||
|
||||
/**
|
||||
* Builder对象
|
||||
*
|
||||
* @var Builder
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* Db对象
|
||||
*
|
||||
* @var DbManager
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* 是否读取主库.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $readMaster = false;
|
||||
|
||||
/**
|
||||
* 数据库连接参数配置.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [];
|
||||
|
||||
/**
|
||||
* 缓存对象
|
||||
*
|
||||
* @var CacheInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* 架构函数 读取数据库配置信息.
|
||||
*
|
||||
* @param array $config 数据库配置数组
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
if (!empty($config)) {
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
|
||||
// 创建Builder对象
|
||||
$class = $this->getBuilderClass();
|
||||
|
||||
$this->builder = new $class($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的builder实例对象
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
public function getBuilder()
|
||||
{
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建查询对象
|
||||
*/
|
||||
public function newQuery()
|
||||
{
|
||||
$class = $this->getQueryClass();
|
||||
|
||||
/** @var BaseQuery $query */
|
||||
$query = new $class($this);
|
||||
|
||||
$timeRule = $this->db->getConfig('time_query_rule');
|
||||
if (!empty($timeRule)) {
|
||||
$query->timeRule($timeRule);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定表名开始查询.
|
||||
*
|
||||
* @param $table
|
||||
*
|
||||
* @return BaseQuery
|
||||
*/
|
||||
public function table($table)
|
||||
{
|
||||
return $this->newQuery()->table($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定表名开始查询(不带前缀).
|
||||
*
|
||||
* @param $name
|
||||
*
|
||||
* @return BaseQuery
|
||||
*/
|
||||
public function name($name)
|
||||
{
|
||||
return $this->newQuery()->name($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前的数据库Db对象
|
||||
*
|
||||
* @param DbManager $db
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDb(DbManager $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前的缓存对象
|
||||
*
|
||||
* @param CacheInterface $cache
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCache(CacheInterface $cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的缓存对象
|
||||
*
|
||||
* @return CacheInterface|null
|
||||
*/
|
||||
public function getCache()
|
||||
{
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库的配置参数.
|
||||
*
|
||||
* @param string $config 配置名称
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig(string $config = '')
|
||||
{
|
||||
if ('' === $config) {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
return $this->config[$config] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库SQL监控.
|
||||
*
|
||||
* @param string $sql 执行的SQL语句 留空自动获取
|
||||
* @param bool $master 主从标记
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function trigger(string $sql = '', bool $master = false): void
|
||||
{
|
||||
$listen = $this->db->getListen();
|
||||
if (empty($listen)) {
|
||||
$listen[] = function ($sql, $time, $master) {
|
||||
if (str_starts_with($sql, 'CONNECT:')) {
|
||||
$this->db->log($sql);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 记录SQL
|
||||
if (is_bool($master)) {
|
||||
// 分布式记录当前操作的主从
|
||||
$master = $master ? 'master|' : 'slave|';
|
||||
} else {
|
||||
$master = '';
|
||||
}
|
||||
|
||||
$this->db->log($sql . ' [ ' . $master . 'RunTime:' . $time . 's ]');
|
||||
};
|
||||
}
|
||||
|
||||
$runtime = number_format((microtime(true) - $this->queryStartTime), 6);
|
||||
$sql = $sql ?: $this->getLastsql();
|
||||
|
||||
if (empty($this->config['deploy'])) {
|
||||
$master = null;
|
||||
}
|
||||
|
||||
foreach ($listen as $callback) {
|
||||
if (is_callable($callback)) {
|
||||
$callback($sql, $runtime, $master);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存数据.
|
||||
*
|
||||
* @param CacheItem $cacheItem 缓存Item
|
||||
*/
|
||||
protected function cacheData(CacheItem $cacheItem)
|
||||
{
|
||||
if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) {
|
||||
$this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
|
||||
} else {
|
||||
$this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析缓存Key.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
* @param string $method 查询方法
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getCacheKey(BaseQuery $query, string $method = ''): string
|
||||
{
|
||||
if (!empty($query->getOptions('key')) && empty($method)) {
|
||||
$key = 'think_' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key');
|
||||
} else {
|
||||
$key = $query->getQueryGuid();
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析缓存.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
* @param array $cache 缓存信息
|
||||
* @param string $method 查询方法
|
||||
*
|
||||
* @return CacheItem
|
||||
*/
|
||||
protected function parseCache(BaseQuery $query, array $cache, string $method = ''): CacheItem
|
||||
{
|
||||
[$key, $expire, $tag] = $cache;
|
||||
|
||||
if ($key instanceof CacheItem) {
|
||||
$cacheItem = $key;
|
||||
} else {
|
||||
if (true === $key) {
|
||||
$key = $this->getCacheKey($query, $method);
|
||||
}
|
||||
|
||||
$cacheItem = new CacheItem($key);
|
||||
$cacheItem->expire($expire);
|
||||
$cacheItem->tag($tag);
|
||||
}
|
||||
|
||||
return $cacheItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取返回或者影响的记录数.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getNumRows(): int
|
||||
{
|
||||
return $this->numRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最终的SQL语句.
|
||||
*
|
||||
* @param string $sql 带参数绑定的sql语句
|
||||
* @param array $bind 参数绑定列表
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRealSql(string $sql, array $bind = []): string
|
||||
{
|
||||
foreach ($bind as $key => $val) {
|
||||
$value = strval(is_array($val) ? $val[0] : $val);
|
||||
$type = is_array($val) ? $val[1] : self::PARAM_STR;
|
||||
|
||||
if (self::PARAM_FLOAT == $type || self::PARAM_STR == $type) {
|
||||
$value = '\'' . addslashes($value) . '\'';
|
||||
} elseif (self::PARAM_INT == $type && '' === $value) {
|
||||
$value = '0';
|
||||
}
|
||||
|
||||
// 判断占位符
|
||||
$sql = is_numeric($key) ?
|
||||
substr_replace($sql, $value, strpos($sql, '?'), 1) :
|
||||
substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key));
|
||||
}
|
||||
|
||||
return rtrim($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 析构方法.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
// 关闭连接
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use think\DbManager;
|
||||
|
||||
/**
|
||||
* Connection interface.
|
||||
*/
|
||||
interface ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* 获取当前连接器类对应的Query类.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryClass(): string;
|
||||
|
||||
/**
|
||||
* 指定表名开始查询.
|
||||
*
|
||||
* @param $table
|
||||
*
|
||||
* @return BaseQuery
|
||||
*/
|
||||
public function table($table);
|
||||
|
||||
/**
|
||||
* 指定表名开始查询(不带前缀).
|
||||
*
|
||||
* @param $name
|
||||
*
|
||||
* @return BaseQuery
|
||||
*/
|
||||
public function name($name);
|
||||
|
||||
/**
|
||||
* 连接数据库方法.
|
||||
*
|
||||
* @param array $config 接参数
|
||||
* @param int $linkNum 连接序号
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function connect(array $config = [], $linkNum = 0);
|
||||
|
||||
/**
|
||||
* 设置当前的数据库Db对象
|
||||
*
|
||||
* @param DbManager $db
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDb(DbManager $db);
|
||||
|
||||
/**
|
||||
* 设置当前的缓存对象
|
||||
*
|
||||
* @param CacheInterface $cache
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCache(CacheInterface $cache);
|
||||
|
||||
/**
|
||||
* 获取数据库的配置参数.
|
||||
*
|
||||
* @param string $config 配置名称
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig(string $config = '');
|
||||
|
||||
/**
|
||||
* 关闭数据库(或者重新连接).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function close();
|
||||
|
||||
/**
|
||||
* 查找单条记录.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function find(BaseQuery $query): array;
|
||||
|
||||
/**
|
||||
* 查找记录.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function select(BaseQuery $query): array;
|
||||
|
||||
/**
|
||||
* 插入记录.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
* @param bool $getLastInsID 返回自增主键
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function insert(BaseQuery $query, bool $getLastInsID = false);
|
||||
|
||||
/**
|
||||
* 批量插入记录.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
* @param mixed $dataSet 数据集
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function insertAll(BaseQuery $query, array $dataSet = []): int;
|
||||
|
||||
/**
|
||||
* 更新记录.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function update(BaseQuery $query): int;
|
||||
|
||||
/**
|
||||
* 删除记录.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function delete(BaseQuery $query): int;
|
||||
|
||||
/**
|
||||
* 得到某个字段的值
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
* @param string $field 字段名
|
||||
* @param mixed $default 默认值
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function value(BaseQuery $query, string $field, $default = null);
|
||||
|
||||
/**
|
||||
* 得到某个列的数组.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
* @param string|array $column 字段名 多个字段用逗号分隔
|
||||
* @param string $key 索引
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function column(BaseQuery $query, string|array $column, string $key = ''): array;
|
||||
|
||||
/**
|
||||
* 执行数据库事务
|
||||
*
|
||||
* @param callable $callback 数据操作方法回调
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function transaction(callable $callback);
|
||||
|
||||
/**
|
||||
* 启动事务
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function startTrans();
|
||||
|
||||
/**
|
||||
* 用于非自动提交状态下面的查询提交.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function commit();
|
||||
|
||||
/**
|
||||
* 事务回滚.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rollback();
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息.
|
||||
*
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTableFields(string $tableName): array;
|
||||
|
||||
/**
|
||||
* 获取最近一次查询的sql语句.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLastSql(): string;
|
||||
|
||||
/**
|
||||
* 获取最近插入的ID.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
* @param string $sequence 自增序列名
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLastInsID(BaseQuery $query, string $sequence = null);
|
||||
}
|
||||
+514
@@ -0,0 +1,514 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db;
|
||||
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\helper\Str;
|
||||
|
||||
/**
|
||||
* SQL获取类.
|
||||
*/
|
||||
class Fetch
|
||||
{
|
||||
/**
|
||||
* Connection对象
|
||||
*
|
||||
* @var Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* Builder对象
|
||||
*
|
||||
* @var Builder
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* 创建一个查询SQL获取对象
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*/
|
||||
public function __construct(protected Query $query)
|
||||
{
|
||||
$this->connection = $query->getConnection();
|
||||
$this->builder = $this->connection->getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚合查询.
|
||||
*
|
||||
* @param string $aggregate 聚合方法
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function aggregate(string $aggregate, string $field): string
|
||||
{
|
||||
$this->query->parseOptions();
|
||||
|
||||
$field = $aggregate . '(' . $this->builder->parseKey($this->query, $field) . ') AS think_' . strtolower($aggregate);
|
||||
|
||||
return $this->value($field, 0, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到某个字段的值
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param mixed $default 默认值
|
||||
* @param bool $one
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function value(string $field, $default = null, bool $one = true): string
|
||||
{
|
||||
$options = $this->query->parseOptions();
|
||||
|
||||
if (isset($options['field'])) {
|
||||
$this->query->removeOption('field');
|
||||
}
|
||||
|
||||
$this->query->setOption('field', (array) $field);
|
||||
|
||||
// 生成查询SQL
|
||||
$sql = $this->builder->select($this->query, $one);
|
||||
|
||||
if (isset($options['field'])) {
|
||||
$this->query->setOption('field', $options['field']);
|
||||
} else {
|
||||
$this->query->removeOption('field');
|
||||
}
|
||||
|
||||
return $this->fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到某个列的数组.
|
||||
*
|
||||
* @param string $field 字段名 多个字段用逗号分隔
|
||||
* @param string $key 索引
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column(string $field, string $key = ''): string
|
||||
{
|
||||
$options = $this->query->parseOptions();
|
||||
|
||||
if (isset($options['field'])) {
|
||||
$this->query->removeOption('field');
|
||||
}
|
||||
|
||||
if ($key && '*' != $field) {
|
||||
$field = $key . ',' . $field;
|
||||
}
|
||||
|
||||
$field = array_map('trim', explode(',', $field));
|
||||
|
||||
$this->query->setOption('field', $field);
|
||||
|
||||
// 生成查询SQL
|
||||
$sql = $this->builder->select($this->query);
|
||||
|
||||
if (isset($options['field'])) {
|
||||
$this->query->setOption('field', $options['field']);
|
||||
} else {
|
||||
$this->query->removeOption('field');
|
||||
}
|
||||
|
||||
return $this->fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入记录.
|
||||
*
|
||||
* @param array $data 数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function insert(array $data = []): string
|
||||
{
|
||||
$options = $this->query->parseOptions();
|
||||
|
||||
if (!empty($data)) {
|
||||
$this->query->setOption('data', $data);
|
||||
}
|
||||
|
||||
$sql = $this->builder->insert($this->query);
|
||||
|
||||
return $this->fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入记录并获取自增ID.
|
||||
*
|
||||
* @param array $data 数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function insertGetId(array $data = []): string
|
||||
{
|
||||
return $this->insert($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据 自动判断insert或者update.
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @param bool $forceInsert 是否强制insert
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function save(array $data = [], bool $forceInsert = false): string
|
||||
{
|
||||
if ($forceInsert) {
|
||||
return $this->insert($data);
|
||||
}
|
||||
|
||||
$data = array_merge($this->query->getOptions('data') ?: [], $data);
|
||||
|
||||
$this->query->setOption('data', $data);
|
||||
|
||||
if ($this->query->getOptions('where')) {
|
||||
$isUpdate = true;
|
||||
} else {
|
||||
$isUpdate = $this->query->parseUpdateData($data);
|
||||
}
|
||||
|
||||
return $isUpdate ? $this->update() : $this->insert();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入记录.
|
||||
*
|
||||
* @param array $dataSet 数据集
|
||||
* @param int $limit 每次写入数据限制
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function insertAll(array $dataSet = [], int $limit = null): string
|
||||
{
|
||||
$options = $this->query->parseOptions();
|
||||
|
||||
if (empty($dataSet)) {
|
||||
$dataSet = $options['data'];
|
||||
}
|
||||
|
||||
if (empty($limit) && !empty($options['limit'])) {
|
||||
$limit = $options['limit'];
|
||||
}
|
||||
|
||||
if ($limit) {
|
||||
$array = array_chunk($dataSet, $limit, true);
|
||||
$fetchSql = [];
|
||||
foreach ($array as $item) {
|
||||
$sql = $this->builder->insertAll($this->query, $item);
|
||||
$bind = $this->query->getBind();
|
||||
|
||||
$fetchSql[] = $this->connection->getRealSql($sql, $bind);
|
||||
}
|
||||
|
||||
return implode(';', $fetchSql);
|
||||
}
|
||||
|
||||
$sql = $this->builder->insertAll($this->query, $dataSet);
|
||||
|
||||
return $this->fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Select方式插入记录.
|
||||
*
|
||||
* @param array $fields 要插入的数据表字段名
|
||||
* @param string $table 要插入的数据表名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function selectInsert(array $fields, string $table): string
|
||||
{
|
||||
$this->query->parseOptions();
|
||||
|
||||
$sql = $this->builder->selectInsert($this->query, $fields, $table);
|
||||
|
||||
return $this->fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新记录.
|
||||
*
|
||||
* @param mixed $data 数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function update(array $data = []): string
|
||||
{
|
||||
$options = $this->query->parseOptions();
|
||||
|
||||
$data = !empty($data) ? $data : $options['data'];
|
||||
|
||||
$pk = $this->query->getPk();
|
||||
|
||||
if (empty($options['where'])) {
|
||||
// 如果存在主键数据 则自动作为更新条件
|
||||
if (is_string($pk) && isset($data[$pk])) {
|
||||
$this->query->where($pk, '=', $data[$pk]);
|
||||
unset($data[$pk]);
|
||||
} elseif (is_array($pk)) {
|
||||
// 增加复合主键支持
|
||||
foreach ($pk as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$this->query->where($field, '=', $data[$field]);
|
||||
} else {
|
||||
// 如果缺少复合主键数据则不执行
|
||||
throw new Exception('miss complex primary data');
|
||||
}
|
||||
unset($data[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->query->getOptions('where'))) {
|
||||
// 如果没有任何更新条件则不执行
|
||||
throw new Exception('miss update condition');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
$this->query->setOption('data', $data);
|
||||
|
||||
// 生成UPDATE SQL语句
|
||||
$sql = $this->builder->update($this->query);
|
||||
|
||||
return $this->fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除记录.
|
||||
*
|
||||
* @param mixed $data 表达式 true 表示强制删除
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function delete($data = null): string
|
||||
{
|
||||
$options = $this->query->parseOptions();
|
||||
|
||||
if (!is_null($data) && true !== $data) {
|
||||
// AR模式分析主键条件
|
||||
$this->query->parsePkWhere($data);
|
||||
}
|
||||
|
||||
if (!empty($options['soft_delete'])) {
|
||||
// 软删除
|
||||
[$field, $condition] = $options['soft_delete'];
|
||||
if ($condition) {
|
||||
$this->query->setOption('soft_delete', null);
|
||||
$this->query->setOption('data', [$field => $condition]);
|
||||
// 生成删除SQL语句
|
||||
$sql = $this->builder->delete($this->query);
|
||||
|
||||
return $this->fetch($sql);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成删除SQL语句
|
||||
$sql = $this->builder->delete($this->query);
|
||||
|
||||
return $this->fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找记录 返回SQL.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function select(array $data = []): string
|
||||
{
|
||||
$this->query->parseOptions();
|
||||
|
||||
if (!empty($data)) {
|
||||
// 主键条件分析
|
||||
$this->query->parsePkWhere($data);
|
||||
}
|
||||
|
||||
// 生成查询SQL
|
||||
$sql = $this->builder->select($this->query);
|
||||
|
||||
return $this->fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找单条记录 返回SQL语句.
|
||||
*
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function find($data = null): string
|
||||
{
|
||||
$this->query->parseOptions();
|
||||
|
||||
if (!is_null($data)) {
|
||||
// AR模式分析主键条件
|
||||
$this->query->parsePkWhere($data);
|
||||
}
|
||||
|
||||
// 生成查询SQL
|
||||
$sql = $this->builder->select($this->query, true);
|
||||
|
||||
// 获取实际执行的SQL语句
|
||||
return $this->fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找多条记录 如果不存在则抛出异常.
|
||||
*
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function selectOrFail($data = null): string
|
||||
{
|
||||
return $this->select($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找单条记录 如果不存在则抛出异常.
|
||||
*
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function findOrFail($data = null): string
|
||||
{
|
||||
return $this->find($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找单条记录 不存在返回空数据(或者空模型).
|
||||
*
|
||||
* @param mixed $data 数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function findOrEmpty($data = null)
|
||||
{
|
||||
return $this->find($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实际的SQL语句.
|
||||
*
|
||||
* @param string $sql
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fetch(string $sql): string
|
||||
{
|
||||
$bind = $this->query->getBind();
|
||||
|
||||
return $this->connection->getRealSql($sql, $bind);
|
||||
}
|
||||
|
||||
/**
|
||||
* COUNT查询.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function count(string $field = '*'): string
|
||||
{
|
||||
$options = $this->query->parseOptions();
|
||||
|
||||
if (!empty($options['group'])) {
|
||||
// 支持GROUP
|
||||
$subSql = $this->query->field('count(' . $field . ') AS think_count')->buildSql();
|
||||
$query = $this->query->newQuery()->table([$subSql => '_group_count_']);
|
||||
|
||||
return $query->fetchsql()->aggregate('COUNT', '*');
|
||||
} else {
|
||||
return $this->aggregate('COUNT', $field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SUM查询.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sum(string $field): string
|
||||
{
|
||||
return $this->aggregate('SUM', $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* MIN查询.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function min(string $field): string
|
||||
{
|
||||
return $this->aggregate('MIN', $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* MAX查询.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function max(string $field): string
|
||||
{
|
||||
return $this->aggregate('MAX', $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* AVG查询.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function avg(string $field): string
|
||||
{
|
||||
return $this->aggregate('AVG', $field);
|
||||
}
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (strtolower(substr($method, 0, 5)) == 'getby') {
|
||||
// 根据某个字段获取记录
|
||||
$field = Str::snake(substr($method, 5));
|
||||
|
||||
return $this->where($field, '=', $args[0])->find();
|
||||
} elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
|
||||
// 根据某个字段获取记录的某个值
|
||||
$name = Str::snake(substr($method, 10));
|
||||
|
||||
return $this->where($name, '=', $args[0])->value($args[1]);
|
||||
}
|
||||
|
||||
$result = call_user_func_array([$this->query, $method], $args);
|
||||
|
||||
return $result === $this->query ? $this : $result;
|
||||
}
|
||||
}
|
||||
+766
@@ -0,0 +1,766 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db;
|
||||
|
||||
use MongoDB\Driver\Command;
|
||||
use MongoDB\Driver\Cursor;
|
||||
use MongoDB\Driver\Exception\AuthenticationException;
|
||||
use MongoDB\Driver\Exception\ConnectionException;
|
||||
use MongoDB\Driver\Exception\InvalidArgumentException;
|
||||
use MongoDB\Driver\Exception\RuntimeException;
|
||||
use MongoDB\Driver\ReadPreference;
|
||||
use MongoDB\Driver\WriteConcern;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\Paginator;
|
||||
|
||||
class Mongo extends BaseQuery
|
||||
{
|
||||
/**
|
||||
* 当前数据库连接对象
|
||||
*
|
||||
* @var \think\db\connector\Mongo
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* 执行指令 返回数据集.
|
||||
*
|
||||
* @param Command $command 指令
|
||||
* @param string $dbName
|
||||
* @param ReadPreference $readPreference readPreference
|
||||
* @param string|array $typeMap 指定返回的typeMap
|
||||
*
|
||||
* @throws AuthenticationException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ConnectionException
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null)
|
||||
{
|
||||
return $this->connection->command($command, $dbName, $readPreference, $typeMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行command.
|
||||
*
|
||||
* @param string|array|object $command 指令
|
||||
* @param mixed $extra 额外参数
|
||||
* @param string $db 数据库名
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function cmd($command, $extra = null, string $db = ''): array
|
||||
{
|
||||
$this->parseOptions();
|
||||
|
||||
return $this->connection->cmd($this, $command, $extra, $db);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定distinct查询.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDistinct(string $field)
|
||||
{
|
||||
$result = $this->cmd('distinct', $field);
|
||||
|
||||
return $result[0]['values'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库的所有collection.
|
||||
*
|
||||
* @param string $db 数据库名称 留空为当前数据库
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function listCollections(string $db = '')
|
||||
{
|
||||
$cursor = $this->cmd('listCollections', null, $db);
|
||||
$result = [];
|
||||
foreach ($cursor as $collection) {
|
||||
$result[] = $collection['name'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* COUNT查询.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(string $field = null): int
|
||||
{
|
||||
$result = $this->cmd('count');
|
||||
|
||||
return $result[0]['n'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚合查询.
|
||||
*
|
||||
* @param string $aggregate 聚合指令
|
||||
* @param string $field 字段名
|
||||
* @param bool $force 强制转为数字类型
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function aggregate(string $aggregate, $field, bool $force = false, bool $one = false)
|
||||
{
|
||||
$result = $this->cmd('aggregate', [strtolower($aggregate), $field]);
|
||||
$value = $result[0]['aggregate'] ?? 0;
|
||||
|
||||
if ($force) {
|
||||
$value += 0;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多聚合操作.
|
||||
*
|
||||
* @param array $aggregate 聚合指令, 可以聚合多个参数, 如 ['sum' => 'field1', 'avg' => 'field2']
|
||||
* @param array $groupBy 类似mysql里面的group字段, 可以传入多个字段, 如 ['field_a', 'field_b', 'field_c']
|
||||
*
|
||||
* @return array 查询结果
|
||||
*/
|
||||
public function multiAggregate(array $aggregate, array $groupBy): array
|
||||
{
|
||||
$result = $this->cmd('multiAggregate', [$aggregate, $groupBy]);
|
||||
|
||||
foreach ($result as &$row) {
|
||||
if (isset($row['_id']) && !empty($row['_id'])) {
|
||||
foreach ($row['_id'] as $k => $v) {
|
||||
$row[$k] = $v;
|
||||
}
|
||||
unset($row['_id']);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段值增长
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param float $step 增长值
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function inc(string $field, float $step = 1)
|
||||
{
|
||||
$this->options['data'][$field] = ['$inc', $step];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段值减少.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param float $step 减少值
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function dec(string $field, float $step = 1)
|
||||
{
|
||||
return $this->inc($field, -1 * $step);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定当前操作的Collection.
|
||||
*
|
||||
* @param string $table 表名
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function table($table)
|
||||
{
|
||||
$this->options['table'] = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* table方法的别名.
|
||||
*
|
||||
* @param string $collection
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function collection(string $collection)
|
||||
{
|
||||
return $this->table($collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置typeMap.
|
||||
*
|
||||
* @param string|array $typeMap
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function typeMap($typeMap)
|
||||
{
|
||||
$this->options['typeMap'] = $typeMap;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* awaitData.
|
||||
*
|
||||
* @param bool $awaitData
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function awaitData(bool $awaitData)
|
||||
{
|
||||
$this->options['awaitData'] = $awaitData;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* batchSize.
|
||||
*
|
||||
* @param int $batchSize
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function batchSize(int $batchSize)
|
||||
{
|
||||
$this->options['batchSize'] = $batchSize;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* exhaust.
|
||||
*
|
||||
* @param bool $exhaust
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function exhaust(bool $exhaust)
|
||||
{
|
||||
$this->options['exhaust'] = $exhaust;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置modifiers.
|
||||
*
|
||||
* @param array $modifiers
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function modifiers(array $modifiers)
|
||||
{
|
||||
$this->options['modifiers'] = $modifiers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置noCursorTimeout.
|
||||
*
|
||||
* @param bool $noCursorTimeout
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noCursorTimeout(bool $noCursorTimeout)
|
||||
{
|
||||
$this->options['noCursorTimeout'] = $noCursorTimeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置oplogReplay.
|
||||
*
|
||||
* @param bool $oplogReplay
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function oplogReplay(bool $oplogReplay)
|
||||
{
|
||||
$this->options['oplogReplay'] = $oplogReplay;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置partial.
|
||||
*
|
||||
* @param bool $partial
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function partial(bool $partial)
|
||||
{
|
||||
$this->options['partial'] = $partial;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* maxTimeMS.
|
||||
*
|
||||
* @param string $maxTimeMS
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function maxTimeMS(string $maxTimeMS)
|
||||
{
|
||||
$this->options['maxTimeMS'] = $maxTimeMS;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* collation.
|
||||
*
|
||||
* @param array $collation
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function collation(array $collation)
|
||||
{
|
||||
$this->options['collation'] = $collation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否REPLACE.
|
||||
*
|
||||
* @param bool $replace 是否使用REPLACE写入数据
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function replace(bool $replace = true)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置返回字段.
|
||||
*
|
||||
* @param mixed $field 字段信息
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function field($field)
|
||||
{
|
||||
if (empty($field) || '*' == $field) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_string($field)) {
|
||||
$field = array_map('trim', explode(',', $field));
|
||||
}
|
||||
|
||||
$projection = [];
|
||||
foreach ($field as $key => $val) {
|
||||
if (is_numeric($key)) {
|
||||
$projection[$val] = 1;
|
||||
} else {
|
||||
$projection[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
$this->options['projection'] = $projection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定要排除的查询字段.
|
||||
*
|
||||
* @param array|string $field 要排除的字段
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withoutField($field)
|
||||
{
|
||||
if (empty($field) || '*' == $field) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_string($field)) {
|
||||
$field = array_map('trim', explode(',', $field));
|
||||
}
|
||||
|
||||
$projection = [];
|
||||
foreach ($field as $key => $val) {
|
||||
if (is_numeric($key)) {
|
||||
$projection[$val] = 0;
|
||||
} else {
|
||||
$projection[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
$this->options['projection'] = $projection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置skip.
|
||||
*
|
||||
* @param int $skip
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function skip(int $skip)
|
||||
{
|
||||
$this->options['skip'] = $skip;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置slaveOk.
|
||||
*
|
||||
* @param bool $slaveOk
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function slaveOk(bool $slaveOk)
|
||||
{
|
||||
$this->options['slaveOk'] = $slaveOk;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定查询数量.
|
||||
*
|
||||
* @param int $offset 起始位置
|
||||
* @param int $length 查询数量
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function limit(int $offset, int $length = null)
|
||||
{
|
||||
if (is_null($length)) {
|
||||
$length = $offset;
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
$this->options['skip'] = $offset;
|
||||
$this->options['limit'] = $length;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置sort.
|
||||
*
|
||||
* @param array|string $field
|
||||
* @param string $order
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function order($field, string $order = '')
|
||||
{
|
||||
if (is_array($field)) {
|
||||
$this->options['sort'] = $field;
|
||||
} else {
|
||||
$this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置tailable.
|
||||
*
|
||||
* @param bool $tailable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function tailable(bool $tailable)
|
||||
{
|
||||
$this->options['tailable'] = $tailable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置writeConcern对象
|
||||
*
|
||||
* @param WriteConcern $writeConcern
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function writeConcern(WriteConcern $writeConcern)
|
||||
{
|
||||
$this->options['writeConcern'] = $writeConcern;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前数据表的主键.
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function getPk()
|
||||
{
|
||||
return $this->pk ?: $this->connection->getConfig('pk');
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行查询但只返回Cursor对象
|
||||
*
|
||||
* @return Cursor
|
||||
*/
|
||||
public function getCursor(): Cursor
|
||||
{
|
||||
$this->parseOptions();
|
||||
|
||||
return $this->connection->getCursor($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的查询标识.
|
||||
*
|
||||
* @param mixed $data 要序列化的数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryGuid($data = null): string
|
||||
{
|
||||
return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询.
|
||||
*
|
||||
* @param int|array $listRows 每页数量 数组表示配置参数
|
||||
* @param int|bool $simple 是否简洁模式或者总记录数
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Paginator
|
||||
*/
|
||||
public function paginate($listRows = null, $simple = false): Paginator
|
||||
{
|
||||
if (is_int($simple)) {
|
||||
$total = $simple;
|
||||
$simple = false;
|
||||
}
|
||||
|
||||
$defaultConfig = [
|
||||
'query' => [], //url额外参数
|
||||
'fragment' => '', //url锚点
|
||||
'var_page' => 'page', //分页变量
|
||||
'list_rows' => 15, //每页数量
|
||||
];
|
||||
|
||||
if (is_array($listRows)) {
|
||||
$config = array_merge($defaultConfig, $listRows);
|
||||
$listRows = intval($config['list_rows']);
|
||||
} else {
|
||||
$config = $defaultConfig;
|
||||
$listRows = intval($listRows ?: $config['list_rows']);
|
||||
}
|
||||
|
||||
$page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
|
||||
|
||||
$page = max($page, 1);
|
||||
|
||||
$config['path'] = $config['path'] ?? Paginator::getCurrentPath();
|
||||
|
||||
if (!isset($total) && !$simple) {
|
||||
$options = $this->getOptions();
|
||||
|
||||
unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
|
||||
|
||||
$total = $this->count();
|
||||
$results = $this->options($options)->page($page, $listRows)->select();
|
||||
} elseif ($simple) {
|
||||
$results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
|
||||
$total = null;
|
||||
} else {
|
||||
$results = $this->page($page, $listRows)->select();
|
||||
}
|
||||
|
||||
$this->removeOption('limit');
|
||||
$this->removeOption('page');
|
||||
|
||||
return Paginator::make($results, $listRows, $page, $total, $simple, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分批数据返回处理.
|
||||
*
|
||||
* @param int $count 每次处理的数据数量
|
||||
* @param callable $callback 处理回调方法
|
||||
* @param string|array $column 分批处理的字段名
|
||||
* @param string $order 字段排序
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
|
||||
{
|
||||
$options = $this->getOptions();
|
||||
$column = $column ?: $this->getPk();
|
||||
|
||||
if (isset($options['order'])) {
|
||||
unset($options['order']);
|
||||
}
|
||||
|
||||
if (is_array($column)) {
|
||||
$times = 1;
|
||||
$query = $this->options($options)->page($times, $count);
|
||||
} else {
|
||||
$query = $this->options($options)->limit($count);
|
||||
|
||||
if (str_contains($column, '.')) {
|
||||
[$alias, $key] = explode('.', $column);
|
||||
} else {
|
||||
$key = $column;
|
||||
}
|
||||
}
|
||||
|
||||
$resultSet = $query->order($column, $order)->select();
|
||||
|
||||
while (count($resultSet) > 0) {
|
||||
if (false === call_user_func($callback, $resultSet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($times)) {
|
||||
$times++;
|
||||
$query = $this->options($options)->page($times, $count);
|
||||
} else {
|
||||
$end = $resultSet->pop();
|
||||
$lastId = is_array($end) ? $end[$key] : $end->getData($key);
|
||||
|
||||
$query = $this->options($options)
|
||||
->limit($count)
|
||||
->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
|
||||
}
|
||||
|
||||
$resultSet = $query->order($column, $order)->select();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析表达式(可用于查询或者写入操作).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parseOptions(): array
|
||||
{
|
||||
$options = $this->options;
|
||||
|
||||
// 获取数据表
|
||||
if (empty($options['table'])) {
|
||||
$options['table'] = $this->getTable();
|
||||
}
|
||||
|
||||
foreach (['where', 'data', 'projection', 'filter', 'json', 'with_attr', 'with_relation_attr'] as $name) {
|
||||
if (!isset($options[$name])) {
|
||||
$options[$name] = [];
|
||||
}
|
||||
}
|
||||
|
||||
$modifiers = empty($options['modifiers']) ? [] : $options['modifiers'];
|
||||
if (isset($options['comment'])) {
|
||||
$modifiers['$comment'] = $options['comment'];
|
||||
}
|
||||
|
||||
if (isset($options['maxTimeMS'])) {
|
||||
$modifiers['$maxTimeMS'] = $options['maxTimeMS'];
|
||||
}
|
||||
|
||||
if (!empty($modifiers)) {
|
||||
$options['modifiers'] = $modifiers;
|
||||
}
|
||||
|
||||
if (!isset($options['typeMap'])) {
|
||||
$options['typeMap'] = $this->getConfig('type_map');
|
||||
}
|
||||
|
||||
if (!isset($options['limit'])) {
|
||||
$options['limit'] = 0;
|
||||
}
|
||||
|
||||
foreach (['master', 'fetch_sql', 'fetch_cursor'] as $name) {
|
||||
if (!isset($options[$name])) {
|
||||
$options[$name] = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['page'])) {
|
||||
// 根据页数计算limit
|
||||
[$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['skip'] = intval($offset);
|
||||
$options['limit'] = intval($listRows);
|
||||
}
|
||||
|
||||
$this->options = $options;
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段类型信息.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFieldsType(): array
|
||||
{
|
||||
if (!empty($this->options['field_type'])) {
|
||||
return $this->options['field_type'];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段类型信息.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFieldType(string $field)
|
||||
{
|
||||
$fieldType = $this->getFieldsType();
|
||||
|
||||
return $fieldType[$field] ?? null;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+614
@@ -0,0 +1,614 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db;
|
||||
|
||||
use PDOStatement;
|
||||
use think\db\exception\DbException as Exception;
|
||||
|
||||
/**
|
||||
* PDO数据查询类.
|
||||
*/
|
||||
class Query extends BaseQuery
|
||||
{
|
||||
use concern\JoinAndViewQuery;
|
||||
use concern\ParamsBind;
|
||||
use concern\TableFieldInfo;
|
||||
|
||||
/**
|
||||
* 表达式方式指定Field排序.
|
||||
*
|
||||
* @param string $field 排序字段
|
||||
* @param array $bind 参数绑定
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function orderRaw(string $field, array $bind = [])
|
||||
{
|
||||
$this->options['order'][] = new Raw($field, $bind);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表达式方式指定查询字段.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function fieldRaw(string $field)
|
||||
{
|
||||
$this->options['field'][] = new Raw($field);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定Field排序 orderField('id',[1,2,3],'desc').
|
||||
*
|
||||
* @param string $field 排序字段
|
||||
* @param array $values 排序值
|
||||
* @param string $order 排序 desc/asc
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function orderField(string $field, array $values, string $order = '')
|
||||
{
|
||||
if (!empty($values)) {
|
||||
$values['sort'] = $order;
|
||||
|
||||
$this->options['order'][$field] = $values;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function orderRand()
|
||||
{
|
||||
$this->options['order'][] = '[rand]';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用表达式设置数据.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param string $value 字段值
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function exp(string $field, string $value)
|
||||
{
|
||||
$this->options['data'][$field] = new Raw($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表达式方式指定当前操作的数据表.
|
||||
*
|
||||
* @param mixed $table 表名
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function tableRaw(string $table)
|
||||
{
|
||||
$this->options['table'] = new Raw($table);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取执行的SQL语句而不进行实际的查询.
|
||||
*
|
||||
* @param bool $fetch 是否返回sql
|
||||
*
|
||||
* @return $this|Fetch
|
||||
*/
|
||||
public function fetchSql(bool $fetch = true)
|
||||
{
|
||||
$this->options['fetch_sql'] = $fetch;
|
||||
|
||||
if ($fetch) {
|
||||
return new Fetch($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批处理执行SQL语句
|
||||
* 批处理的指令都认为是execute操作.
|
||||
*
|
||||
* @param array $sql SQL批处理指令
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function batchQuery(array $sql = []): bool
|
||||
{
|
||||
return $this->connection->batchQuery($this, $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* USING支持 用于多表删除.
|
||||
*
|
||||
* @param mixed $using USING
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function using($using)
|
||||
{
|
||||
$this->options['using'] = $using;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储过程调用.
|
||||
*
|
||||
* @param bool $procedure 是否为存储过程查询
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function procedure(bool $procedure = true)
|
||||
{
|
||||
$this->options['procedure'] = $procedure;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定group查询.
|
||||
*
|
||||
* @param string|array $group GROUP
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function group($group)
|
||||
{
|
||||
$this->options['group'] = $group;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定having查询.
|
||||
*
|
||||
* @param string $having having
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function having(string $having)
|
||||
{
|
||||
$this->options['having'] = $having;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定distinct查询.
|
||||
*
|
||||
* @param bool $distinct 是否唯一
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function distinct(bool $distinct = true)
|
||||
{
|
||||
$this->options['distinct'] = $distinct;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定强制索引.
|
||||
*
|
||||
* @param string $force 索引名称
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function force(string $force)
|
||||
{
|
||||
$this->options['force'] = $force;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询注释.
|
||||
*
|
||||
* @param string $comment 注释
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function comment(string $comment)
|
||||
{
|
||||
$this->options['comment'] = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否REPLACE.
|
||||
*
|
||||
* @param bool $replace 是否使用REPLACE写入数据
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function replace(bool $replace = true)
|
||||
{
|
||||
$this->options['replace'] = $replace;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前查询所在的分区.
|
||||
*
|
||||
* @param string|array $partition 分区名称
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function partition($partition)
|
||||
{
|
||||
$this->options['partition'] = $partition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置DUPLICATE.
|
||||
*
|
||||
* @param array|string|Raw $duplicate DUPLICATE信息
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function duplicate($duplicate)
|
||||
{
|
||||
$this->options['duplicate'] = $duplicate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置查询的额外参数.
|
||||
*
|
||||
* @param string $extra 额外信息
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function extra(string $extra)
|
||||
{
|
||||
$this->options['extra'] = $extra;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建子查询SQL.
|
||||
*
|
||||
* @param bool $sub 是否添加括号
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buildSql(bool $sub = true): string
|
||||
{
|
||||
return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前数据表的主键.
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function getPk()
|
||||
{
|
||||
if (empty($this->pk)) {
|
||||
$this->pk = $this->connection->getPk($this->getTable());
|
||||
}
|
||||
|
||||
return $this->pk;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定数据表自增主键.
|
||||
*
|
||||
* @param string $autoinc 自增键
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function autoinc(string $autoinc)
|
||||
{
|
||||
$this->autoinc = $autoinc;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前数据表的自增主键.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAutoInc()
|
||||
{
|
||||
$tableName = $this->getTable();
|
||||
|
||||
if (empty($this->autoinc) && $tableName) {
|
||||
$this->autoinc = $this->connection->getAutoInc($tableName);
|
||||
}
|
||||
|
||||
return $this->autoinc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段值增长
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param float $step 增长值
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function inc(string $field, float $step = 1)
|
||||
{
|
||||
$this->options['data'][$field] = ['INC', $step];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段值减少.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param float $step 增长值
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function dec(string $field, float $step = 1)
|
||||
{
|
||||
$this->options['data'][$field] = ['DEC', $step];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段值增长(支持延迟写入)
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param int $step 步进值
|
||||
* @param int $lazyTime 延迟时间(秒)
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
public function setInc(string $field, int $step = 1, $lazyTime = 0)
|
||||
{
|
||||
if (empty($this->options['where']) && $this->model) {
|
||||
$this->where($this->model->getWhere());
|
||||
}
|
||||
|
||||
if (empty($this->options['where'])) {
|
||||
// 如果没有任何更新条件则不执行
|
||||
throw new Exception('miss update condition');
|
||||
}
|
||||
|
||||
if ($lazyTime > 0) {
|
||||
$guid = $this->getLazyFieldCacheKey($field);
|
||||
$step = $this->lazyWrite('inc', $guid, $step, $lazyTime);
|
||||
if (false === $step) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->inc($field, $step)->update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段值减少(支持延迟写入)
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param int $step 步进值
|
||||
* @param int $lazyTime 延迟时间(秒)
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
public function setDec(string $field, int $step = 1, int $lazyTime = 0)
|
||||
{
|
||||
if (empty($this->options['where']) && $this->model) {
|
||||
$this->where($this->model->getWhere());
|
||||
}
|
||||
|
||||
if (empty($this->options['where'])) {
|
||||
// 如果没有任何更新条件则不执行
|
||||
throw new Exception('miss update condition');
|
||||
}
|
||||
|
||||
if ($lazyTime > 0) {
|
||||
$guid = $this->getLazyFieldCacheKey($field);
|
||||
$step = $this->lazyWrite('dec', $guid, $step, $lazyTime);
|
||||
if (false === $step) {
|
||||
return true;
|
||||
}
|
||||
return $this->inc($field, $step)->update();
|
||||
}
|
||||
|
||||
return $this->dec($field, $step)->update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 延时更新检查 返回false表示需要延时
|
||||
* 否则返回实际写入的数值
|
||||
* @access protected
|
||||
* @param string $type 自增或者自减
|
||||
* @param string $guid 写入标识
|
||||
* @param int $step 写入步进值
|
||||
* @param int $lazyTime 延时时间(s)
|
||||
* @return false|integer
|
||||
*/
|
||||
protected function lazyWrite(string $type, string $guid, int $step, int $lazyTime)
|
||||
{
|
||||
$cache = $this->getCache();
|
||||
if (!$cache->has($guid . '_time')) {
|
||||
// 计时开始
|
||||
$cache->set($guid . '_time', time());
|
||||
$cache->$type($guid, $step);
|
||||
} elseif (time() > $cache->get($guid . '_time') + $lazyTime) {
|
||||
// 删除缓存
|
||||
$value = $cache->$type($guid, $step);
|
||||
$cache->delete($guid);
|
||||
$cache->delete($guid . '_time');
|
||||
return 0 === $value ? false : $value;
|
||||
} else {
|
||||
// 更新缓存
|
||||
$cache->$type($guid, $step);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取延迟写入字段值.
|
||||
*
|
||||
* @param string $field 字段名称
|
||||
* @param mixed $id 主键值
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getLazyFieldValue(string $field, $id = null): int
|
||||
{
|
||||
return (int) $this->getCache()->get($this->getLazyFieldCacheKey($field, $id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取延迟写入字段的缓存Key
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param mixed $id 主键值
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getLazyFieldCacheKey(string $field, $id = null): string
|
||||
{
|
||||
return 'lazy_' . $this->getTable() . '_' . $field . '_' . ($id ?: $this->getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的查询标识.
|
||||
*
|
||||
* @param mixed $data 要序列化的数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryGuid($data = null): string
|
||||
{
|
||||
return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)) . serialize($this->getBind(false)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行查询但只返回PDOStatement对象
|
||||
*
|
||||
* @return PDOStatement
|
||||
*/
|
||||
public function getPdo(): PDOStatement
|
||||
{
|
||||
return $this->connection->pdo($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用游标查找记录.
|
||||
*
|
||||
* @param mixed $data 数据
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function cursor($data = null)
|
||||
{
|
||||
if (!is_null($data)) {
|
||||
// 主键条件分析
|
||||
$this->parsePkWhere($data);
|
||||
}
|
||||
|
||||
$this->options['data'] = $data;
|
||||
|
||||
$connection = clone $this->connection;
|
||||
|
||||
return $connection->cursor($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分批数据返回处理.
|
||||
*
|
||||
* @param int $count 每次处理的数据数量
|
||||
* @param callable $callback 处理回调方法
|
||||
* @param string|array $column 分批处理的字段名
|
||||
* @param string $order 字段排序
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function chunk(int $count, callable $callback, string|array $column = null, string $order = 'asc'): bool
|
||||
{
|
||||
$options = $this->getOptions();
|
||||
$column = $column ?: $this->getPk();
|
||||
|
||||
if (isset($options['order'])) {
|
||||
unset($options['order']);
|
||||
}
|
||||
|
||||
$bind = $this->bind;
|
||||
|
||||
if (is_array($column)) {
|
||||
$times = 1;
|
||||
$query = $this->options($options)->page($times, $count);
|
||||
} else {
|
||||
$query = $this->options($options)->limit($count);
|
||||
|
||||
if (str_contains($column, '.')) {
|
||||
[$alias, $key] = explode('.', $column);
|
||||
} else {
|
||||
$key = $column;
|
||||
}
|
||||
}
|
||||
|
||||
$resultSet = $query->order($column, $order)->select();
|
||||
|
||||
while (count($resultSet) > 0) {
|
||||
if (false === call_user_func($callback, $resultSet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($times)) {
|
||||
$times++;
|
||||
$query = $this->options($options)->page($times, $count);
|
||||
} else {
|
||||
$end = $resultSet->pop();
|
||||
$lastId = is_array($end) ? $end[$key] : $end->getData($key);
|
||||
|
||||
$query = $this->options($options)
|
||||
->limit($count)
|
||||
->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
|
||||
}
|
||||
|
||||
$resultSet = $query->bind($bind)->order($column, $order)->select();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db;
|
||||
|
||||
use Stringable;
|
||||
|
||||
/**
|
||||
* SQL Raw.
|
||||
*/
|
||||
class Raw
|
||||
{
|
||||
/**
|
||||
* 创建一个查询表达式.
|
||||
*
|
||||
* @param string|Stringable $value
|
||||
* @param array $bind
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(protected string|Stringable $value, protected array $bind = [])
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表达式.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数绑定.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBind(): array
|
||||
{
|
||||
return $this->bind;
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db;
|
||||
|
||||
use ArrayAccess;
|
||||
|
||||
/**
|
||||
* 数组查询对象
|
||||
*/
|
||||
class Where implements ArrayAccess
|
||||
{
|
||||
/**
|
||||
* 创建一个查询表达式.
|
||||
*
|
||||
* @param array $where 查询条件数组
|
||||
* @param bool $enclose 是否增加括号
|
||||
*/
|
||||
public function __construct(protected array $where = [], protected bool $enclose = false)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否添加括号.
|
||||
*
|
||||
* @param bool $enclose
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function enclose(bool $enclose = true)
|
||||
{
|
||||
$this->enclose = $enclose;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析为Query对象可识别的查询条件数组.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parse(): array
|
||||
{
|
||||
$where = [];
|
||||
|
||||
foreach ($this->where as $key => $val) {
|
||||
if ($val instanceof Raw) {
|
||||
$where[] = [$key, 'exp', $val];
|
||||
} elseif (is_null($val)) {
|
||||
$where[] = [$key, 'NULL', ''];
|
||||
} elseif (is_array($val)) {
|
||||
$where[] = $this->parseItem($key, $val);
|
||||
} else {
|
||||
$where[] = [$key, '=', $val];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->enclose ? [$where] : $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析查询表达式.
|
||||
*
|
||||
* @param string $field 查询字段
|
||||
* @param array $where 查询条件
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseItem(string $field, array $where = []): array
|
||||
{
|
||||
$op = $where[0];
|
||||
$condition = $where[1] ?? null;
|
||||
|
||||
if (is_array($op)) {
|
||||
// 同一字段多条件查询
|
||||
array_unshift($where, $field);
|
||||
} elseif (is_null($condition)) {
|
||||
if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
|
||||
// null查询
|
||||
$where = [$field, $op, ''];
|
||||
} elseif (is_null($op) || '=' == $op) {
|
||||
$where = [$field, 'NULL', ''];
|
||||
} elseif ('<>' == $op) {
|
||||
$where = [$field, 'NOTNULL', ''];
|
||||
} else {
|
||||
// 字段相等查询
|
||||
$where = [$field, '=', $op];
|
||||
}
|
||||
} else {
|
||||
$where = [$field, $op, $condition];
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改器 设置数据对象的值
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param mixed $value 值
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->where[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取器 获取数据对象的值
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->where[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测数据对象的值
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($this->where[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁数据对象的值
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __unset($name)
|
||||
{
|
||||
unset($this->where[$name]);
|
||||
}
|
||||
|
||||
// ArrayAccess
|
||||
public function offsetSet(mixed $name, mixed $value): void
|
||||
{
|
||||
$this->__set($name, $value);
|
||||
}
|
||||
|
||||
public function offsetExists(mixed $name): bool
|
||||
{
|
||||
return $this->__isset($name);
|
||||
}
|
||||
|
||||
public function offsetUnset(mixed $name): void
|
||||
{
|
||||
$this->__unset($name);
|
||||
}
|
||||
|
||||
public function offsetGet(mixed $name)
|
||||
{
|
||||
return $this->__get($name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\builder;
|
||||
|
||||
use MongoDB\BSON\Javascript;
|
||||
use MongoDB\BSON\ObjectID;
|
||||
use MongoDB\BSON\Regex;
|
||||
use MongoDB\Driver\BulkWrite;
|
||||
use MongoDB\Driver\Command;
|
||||
use MongoDB\Driver\Exception\InvalidArgumentException;
|
||||
use MongoDB\Driver\Query as MongoQuery;
|
||||
use think\db\connector\Mongo as Connection;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\db\Mongo as Query;
|
||||
|
||||
class Mongo
|
||||
{
|
||||
// connection对象实例
|
||||
protected $connection;
|
||||
// 最后插入ID
|
||||
protected $insertId = [];
|
||||
// 查询表达式
|
||||
protected $exp = ['<>' => 'ne', '=' => 'eq', '>' => 'gt', '>=' => 'gte', '<' => 'lt', '<=' => 'lte', 'in' => 'in', 'not in' => 'nin', 'nin' => 'nin', 'mod' => 'mod', 'exists' => 'exists', 'null' => 'null', 'notnull' => 'not null', 'not null' => 'not null', 'regex' => 'regex', 'type' => 'type', 'all' => 'all', '> time' => '> time', '< time' => '< time', 'between' => 'between', 'not between' => 'not between', 'between time' => 'between time', 'not between time' => 'not between time', 'notbetween time' => 'not between time', 'like' => 'like', 'near' => 'near', 'size' => 'size'];
|
||||
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param Connection $connection 数据库连接对象实例
|
||||
*/
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的连接对象实例.
|
||||
*
|
||||
* @return Connection
|
||||
*/
|
||||
public function getConnection(): Connection
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* key分析.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseKey(Query $query, string $key): string
|
||||
{
|
||||
if (str_starts_with($key, '__TABLE__.')) {
|
||||
[$collection, $key] = explode('.', $key, 2);
|
||||
}
|
||||
|
||||
if ('id' == $key && $this->connection->getConfig('pk_convert_id')) {
|
||||
$key = '_id';
|
||||
}
|
||||
|
||||
return trim($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* value分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseValue(Query $query, $value, $field = '')
|
||||
{
|
||||
if ('_id' == $field && 'ObjectID' == $this->connection->getConfig('pk_type') && is_string($value)) {
|
||||
try {
|
||||
return new ObjectID($value);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return new ObjectID();
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* insert数据分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $data 数据
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseData(Query $query, array $data): array
|
||||
{
|
||||
if (empty($data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($data as $key => $val) {
|
||||
$item = $this->parseKey($query, $key);
|
||||
|
||||
if (is_object($val)) {
|
||||
$result[$item] = $val;
|
||||
} elseif (isset($val[0]) && 'exp' == $val[0]) {
|
||||
$result[$item] = $val[1];
|
||||
} else {
|
||||
$result[$item] = $this->parseValue($query, $val, $key);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set数据分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $data 数据
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseSet(Query $query, array $data): array
|
||||
{
|
||||
if (empty($data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($data as $key => $val) {
|
||||
$item = $this->parseKey($query, $key);
|
||||
|
||||
if (is_array($val) && isset($val[0]) && is_string($val[0]) && str_starts_with($val[0], '$')) {
|
||||
$result[$val[0]][$item] = $this->parseValue($query, $val[1], $key);
|
||||
} else {
|
||||
$result['$set'][$item] = $this->parseValue($query, $val, $key);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成查询过滤条件.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $where
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parseWhere(Query $query, array $where): array
|
||||
{
|
||||
if (empty($where)) {
|
||||
$where = [];
|
||||
}
|
||||
|
||||
$filter = [];
|
||||
foreach ($where as $logic => $val) {
|
||||
$logic = '$'.strtolower($logic);
|
||||
foreach ($val as $field => $value) {
|
||||
if (is_array($value)) {
|
||||
if (key($value) !== 0) {
|
||||
throw new Exception('where express error:'.var_export($value, true));
|
||||
}
|
||||
$field = array_shift($value);
|
||||
} elseif (!($value instanceof \Closure)) {
|
||||
throw new Exception('where express error:'.var_export($value, true));
|
||||
}
|
||||
|
||||
if ($value instanceof \Closure) {
|
||||
// 使用闭包查询
|
||||
$query = new Query($this->connection);
|
||||
call_user_func_array($value, [&$query]);
|
||||
$filter[$logic][] = $this->parseWhere($query, $query->getOptions('where'));
|
||||
} else {
|
||||
if (str_contains($field, '|')) {
|
||||
// 不同字段使用相同查询条件(OR)
|
||||
$array = explode('|', $field);
|
||||
foreach ($array as $k) {
|
||||
$filter['$or'][] = $this->parseWhereItem($query, $k, $value);
|
||||
}
|
||||
} elseif (str_contains($field, '&')) {
|
||||
// 不同字段使用相同查询条件(AND)
|
||||
$array = explode('&', $field);
|
||||
foreach ($array as $k) {
|
||||
$filter['$and'][] = $this->parseWhereItem($query, $k, $value);
|
||||
}
|
||||
} else {
|
||||
// 对字段使用表达式查询
|
||||
$field = is_string($field) ? $field : '';
|
||||
$filter[$logic][] = $this->parseWhereItem($query, $field, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$options = $query->getOptions();
|
||||
if (!empty($options['soft_delete'])) {
|
||||
// 附加软删除条件
|
||||
[$field, $condition] = $options['soft_delete'];
|
||||
$filter['$and'][] = $this->parseWhereItem($query, $field, $condition);
|
||||
}
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
// where子单元分析
|
||||
protected function parseWhereItem(Query $query, $field, $val): array
|
||||
{
|
||||
$key = $field ? $this->parseKey($query, $field) : '';
|
||||
// 查询规则和条件
|
||||
if (!is_array($val)) {
|
||||
$val = ['=', $val];
|
||||
}
|
||||
[$exp, $value] = $val;
|
||||
|
||||
// 对一个字段使用多个查询条件
|
||||
if (is_array($exp)) {
|
||||
$data = [];
|
||||
foreach ($val as $value) {
|
||||
$exp = $value[0];
|
||||
$value = $value[1];
|
||||
if (!in_array($exp, $this->exp)) {
|
||||
$exp = strtolower($exp);
|
||||
if (isset($this->exp[$exp])) {
|
||||
$exp = $this->exp[$exp];
|
||||
}
|
||||
}
|
||||
$k = '$'.$exp;
|
||||
$data[$k] = $value;
|
||||
}
|
||||
$result[$key] = $data;
|
||||
|
||||
return $result;
|
||||
} elseif (!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);
|
||||
}
|
||||
}
|
||||
|
||||
$result = [];
|
||||
if ('=' == $exp) {
|
||||
// 普通查询
|
||||
$result[$key] = $this->parseValue($query, $value, $key);
|
||||
} elseif (in_array($exp, ['neq', 'ne', 'gt', 'egt', 'gte', 'lt', 'lte', 'elt', 'mod'])) {
|
||||
// 比较运算
|
||||
$k = '$'.$exp;
|
||||
$result[$key] = [$k => $this->parseValue($query, $value, $key)];
|
||||
} elseif ('null' == $exp) {
|
||||
// NULL 查询
|
||||
$result[$key] = null;
|
||||
} elseif ('not null' == $exp) {
|
||||
$result[$key] = ['$ne' => null];
|
||||
} elseif ('all' == $exp) {
|
||||
// 满足所有指定条件
|
||||
$result[$key] = ['$all', $this->parseValue($query, $value, $key)];
|
||||
} elseif ('between' == $exp) {
|
||||
// 区间查询
|
||||
$value = is_array($value) ? $value : explode(',', $value);
|
||||
$result[$key] = ['$gte' => $this->parseValue($query, $value[0], $key), '$lte' => $this->parseValue($query, $value[1], $key)];
|
||||
} elseif ('not between' == $exp) {
|
||||
// 范围查询
|
||||
$value = is_array($value) ? $value : explode(',', $value);
|
||||
$result[$key] = ['$lt' => $this->parseValue($query, $value[0], $key), '$gt' => $this->parseValue($query, $value[1], $key)];
|
||||
} elseif ('exists' == $exp) {
|
||||
// 字段是否存在
|
||||
$result[$key] = ['$exists' => (bool) $value];
|
||||
} elseif ('type' == $exp) {
|
||||
// 类型查询
|
||||
$result[$key] = ['$type' => intval($value)];
|
||||
} elseif ('exp' == $exp) {
|
||||
// 表达式查询
|
||||
$result['$where'] = $value instanceof Javascript ? $value : new Javascript($value);
|
||||
} elseif ('like' == $exp) {
|
||||
// 模糊查询 采用正则方式
|
||||
$result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i');
|
||||
} elseif (in_array($exp, ['nin', 'in'])) {
|
||||
// IN 查询
|
||||
$value = is_array($value) ? $value : explode(',', $value);
|
||||
foreach ($value as $k => $val) {
|
||||
$value[$k] = $this->parseValue($query, $val, $key);
|
||||
}
|
||||
$result[$key] = ['$'.$exp => $value];
|
||||
} elseif ('regex' == $exp) {
|
||||
$result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i');
|
||||
} elseif ('< time' == $exp) {
|
||||
$result[$key] = ['$lt' => $this->parseDateTime($query, $value, $field)];
|
||||
} elseif ('> time' == $exp) {
|
||||
$result[$key] = ['$gt' => $this->parseDateTime($query, $value, $field)];
|
||||
} elseif ('between time' == $exp) {
|
||||
// 区间查询
|
||||
$value = is_array($value) ? $value : explode(',', $value);
|
||||
$result[$key] = ['$gte' => $this->parseDateTime($query, $value[0], $field), '$lte' => $this->parseDateTime($query, $value[1], $field)];
|
||||
} elseif ('not between time' == $exp) {
|
||||
// 范围查询
|
||||
$value = is_array($value) ? $value : explode(',', $value);
|
||||
$result[$key] = ['$lt' => $this->parseDateTime($query, $value[0], $field), '$gt' => $this->parseDateTime($query, $value[1], $field)];
|
||||
} elseif ('near' == $exp) {
|
||||
// 经纬度查询
|
||||
$result[$key] = ['$near' => $this->parseValue($query, $value, $key)];
|
||||
} elseif ('size' == $exp) {
|
||||
// 元素长度查询
|
||||
$result[$key] = ['$size' => intval($value)];
|
||||
} else {
|
||||
// 普通查询
|
||||
$result[$key] = $this->parseValue($query, $value, $key);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期时间条件解析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $value
|
||||
* @param string $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDateTime(Query $query, $value, $key)
|
||||
{
|
||||
// 获取时间字段类型
|
||||
$type = $query->getFieldType($key);
|
||||
|
||||
if ($type) {
|
||||
if (is_string($value)) {
|
||||
$value = strtotime($value) ?: $value;
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
if (preg_match('/(datetime|timestamp)/is', $type)) {
|
||||
// 日期及时间戳类型
|
||||
$value = date('Y-m-d H:i:s', $value);
|
||||
} elseif (preg_match('/(date)/is', $type)) {
|
||||
// 日期及时间戳类型
|
||||
$value = date('Y-m-d', $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后写入的ID 如果是insertAll方法的话 返回所有写入的ID.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLastInsID()
|
||||
{
|
||||
return $this->insertId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成insert BulkWrite对象
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return BulkWrite
|
||||
*/
|
||||
public function insert(Query $query): BulkWrite
|
||||
{
|
||||
// 分析并处理数据
|
||||
$options = $query->getOptions();
|
||||
|
||||
$data = $this->parseData($query, $options['data']);
|
||||
|
||||
$bulk = new BulkWrite();
|
||||
|
||||
if ($insertId = $bulk->insert($data)) {
|
||||
$this->insertId = $insertId;
|
||||
}
|
||||
|
||||
$this->log('insert', $data, $options);
|
||||
|
||||
return $bulk;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成insertall BulkWrite对象
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $dataSet 数据集
|
||||
*
|
||||
* @return BulkWrite
|
||||
*/
|
||||
public function insertAll(Query $query, array $dataSet): BulkWrite
|
||||
{
|
||||
$bulk = new BulkWrite();
|
||||
$options = $query->getOptions();
|
||||
|
||||
$this->insertId = [];
|
||||
foreach ($dataSet as $data) {
|
||||
// 分析并处理数据
|
||||
$data = $this->parseData($query, $data);
|
||||
if ($insertId = $bulk->insert($data)) {
|
||||
$this->insertId[] = $insertId;
|
||||
}
|
||||
}
|
||||
|
||||
$this->log('insert', $dataSet, $options);
|
||||
|
||||
return $bulk;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成update BulkWrite对象
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return BulkWrite
|
||||
*/
|
||||
public function update(Query $query): BulkWrite
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
$data = $this->parseSet($query, $options['data']);
|
||||
$where = $this->parseWhere($query, $options['where']);
|
||||
|
||||
if (1 == $options['limit']) {
|
||||
$updateOptions = ['multi' => false];
|
||||
} else {
|
||||
$updateOptions = ['multi' => true];
|
||||
}
|
||||
|
||||
$bulk = new BulkWrite();
|
||||
|
||||
$bulk->update($where, $data, $updateOptions);
|
||||
|
||||
$this->log('update', $data, $where);
|
||||
|
||||
return $bulk;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成delete BulkWrite对象
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return BulkWrite
|
||||
*/
|
||||
public function delete(Query $query): BulkWrite
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
$where = $this->parseWhere($query, $options['where']);
|
||||
|
||||
$bulk = new BulkWrite();
|
||||
|
||||
if (1 == $options['limit']) {
|
||||
$deleteOptions = ['limit' => 1];
|
||||
} else {
|
||||
$deleteOptions = ['limit' => 0];
|
||||
}
|
||||
|
||||
$bulk->delete($where, $deleteOptions);
|
||||
|
||||
$this->log('remove', $where, $deleteOptions);
|
||||
|
||||
return $bulk;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Mongo查询对象
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param bool $one 是否仅获取一个记录
|
||||
*
|
||||
* @return MongoQuery
|
||||
*/
|
||||
public function select(Query $query, bool $one = false): MongoQuery
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
$where = $this->parseWhere($query, $options['where']);
|
||||
|
||||
if ($one) {
|
||||
$options['limit'] = 1;
|
||||
}
|
||||
|
||||
$query = new MongoQuery($where, $options);
|
||||
|
||||
$this->log('find', $where, $options);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Count命令.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return Command
|
||||
*/
|
||||
public function count(Query $query): Command
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
$cmd['count'] = $options['table'];
|
||||
$cmd['query'] = (object) $this->parseWhere($query, $options['where']);
|
||||
|
||||
foreach (['hint', 'limit', 'maxTimeMS', 'skip'] as $option) {
|
||||
if (isset($options[$option])) {
|
||||
$cmd[$option] = $options[$option];
|
||||
}
|
||||
}
|
||||
|
||||
$command = new Command($cmd);
|
||||
$this->log('cmd', 'count', $cmd);
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚合查询命令.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $extra 指令和字段
|
||||
*
|
||||
* @return Command
|
||||
*/
|
||||
public function aggregate(Query $query, array $extra): Command
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
[$fun, $field] = $extra;
|
||||
|
||||
if ('id' == $field && $this->connection->getConfig('pk_convert_id')) {
|
||||
$field = '_id';
|
||||
}
|
||||
|
||||
$group = isset($options['group']) ? '$'.$options['group'] : null;
|
||||
|
||||
$pipeline = [
|
||||
['$match' => (object) $this->parseWhere($query, $options['where'])],
|
||||
['$group' => ['_id' => $group, 'aggregate' => ['$'.$fun => '$'.$field]]],
|
||||
];
|
||||
|
||||
$cmd = [
|
||||
'aggregate' => $options['table'],
|
||||
'allowDiskUse' => true,
|
||||
'pipeline' => $pipeline,
|
||||
'cursor' => new \stdClass(),
|
||||
];
|
||||
|
||||
foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) {
|
||||
if (isset($options[$option])) {
|
||||
$cmd[$option] = $options[$option];
|
||||
}
|
||||
}
|
||||
|
||||
$command = new Command($cmd);
|
||||
|
||||
$this->log('aggregate', $cmd);
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多聚合查询命令, 可以对多个字段进行 group by 操作.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $extra 指令和字段
|
||||
*
|
||||
* @return Command
|
||||
*/
|
||||
public function multiAggregate(Query $query, $extra): Command
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
[$aggregate, $groupBy] = $extra;
|
||||
|
||||
$groups = ['_id' => []];
|
||||
|
||||
foreach ($groupBy as $field) {
|
||||
$groups['_id'][$field] = '$'.$field;
|
||||
}
|
||||
|
||||
foreach ($aggregate as $fun => $field) {
|
||||
$groups[$field.'_'.$fun] = ['$'.$fun => '$'.$field];
|
||||
}
|
||||
|
||||
$pipeline = [
|
||||
['$match' => (object) $this->parseWhere($query, $options['where'])],
|
||||
['$group' => $groups],
|
||||
];
|
||||
|
||||
$cmd = [
|
||||
'aggregate' => $options['table'],
|
||||
'allowDiskUse' => true,
|
||||
'pipeline' => $pipeline,
|
||||
'cursor' => new \stdClass(),
|
||||
];
|
||||
|
||||
foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) {
|
||||
if (isset($options[$option])) {
|
||||
$cmd[$option] = $options[$option];
|
||||
}
|
||||
}
|
||||
|
||||
$command = new Command($cmd);
|
||||
$this->log('group', $cmd);
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成distinct命令.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return Command
|
||||
*/
|
||||
public function distinct(Query $query, $field): Command
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
$cmd = [
|
||||
'distinct' => $options['table'],
|
||||
'key' => $field,
|
||||
];
|
||||
|
||||
if (!empty($options['where'])) {
|
||||
$cmd['query'] = (object) $this->parseWhere($query, $options['where']);
|
||||
}
|
||||
|
||||
if (isset($options['maxTimeMS'])) {
|
||||
$cmd['maxTimeMS'] = $options['maxTimeMS'];
|
||||
}
|
||||
|
||||
$command = new Command($cmd);
|
||||
|
||||
$this->log('cmd', 'distinct', $cmd);
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有的collection.
|
||||
*
|
||||
* @return Command
|
||||
*/
|
||||
public function listcollections(): Command
|
||||
{
|
||||
$cmd = ['listCollections' => 1];
|
||||
$command = new Command($cmd);
|
||||
|
||||
$this->log('cmd', 'listCollections', $cmd);
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数据表的状态信息.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return Command
|
||||
*/
|
||||
public function collStats(Query $query): Command
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
$cmd = ['collStats' => $options['table']];
|
||||
$command = new Command($cmd);
|
||||
|
||||
$this->log('cmd', 'collStats', $cmd);
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
protected function log($type, $data, $options = [])
|
||||
{
|
||||
$this->connection->mongoLog($type, $data, $options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\builder;
|
||||
|
||||
use PDO;
|
||||
use think\db\Builder;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\Raw;
|
||||
|
||||
/**
|
||||
* mysql数据库驱动.
|
||||
*/
|
||||
class Mysql extends Builder
|
||||
{
|
||||
/**
|
||||
* 查询表达式解析.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $parser = [
|
||||
'parseCompare' => ['=', '<>', '>', '>=', '<', '<='],
|
||||
'parseLike' => ['LIKE', 'NOT LIKE'],
|
||||
'parseBetween' => ['NOT BETWEEN', 'BETWEEN'],
|
||||
'parseIn' => ['NOT IN', 'IN'],
|
||||
'parseExp' => ['EXP'],
|
||||
'parseRegexp' => ['REGEXP', 'NOT REGEXP'],
|
||||
'parseNull' => ['NOT NULL', 'NULL'],
|
||||
'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'],
|
||||
'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'],
|
||||
'parseExists' => ['NOT EXISTS', 'EXISTS'],
|
||||
'parseColumn' => ['COLUMN'],
|
||||
'parseFindInSet' => ['FIND IN SET'],
|
||||
];
|
||||
|
||||
/**
|
||||
* SELECT SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%PARTITION%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%';
|
||||
|
||||
/**
|
||||
* INSERT SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% SET %SET% %DUPLICATE%%COMMENT%';
|
||||
|
||||
/**
|
||||
* INSERT ALL SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% (%FIELD%) VALUES %DATA% %DUPLICATE%%COMMENT%';
|
||||
|
||||
/**
|
||||
* UPDATE SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $updateSql = 'UPDATE%EXTRA% %TABLE%%PARTITION% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%';
|
||||
|
||||
/**
|
||||
* DELETE SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%PARTITION%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
|
||||
|
||||
/**
|
||||
* 生成查询SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param bool $one 是否仅获取一个记录
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function select(Query $query, bool $one = false): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
return str_replace(
|
||||
['%TABLE%', '%PARTITION%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
|
||||
[
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parsePartition($query, $options['partition']),
|
||||
$this->parseDistinct($query, $options['distinct']),
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
$this->parseField($query, $options['field'] ?? []),
|
||||
$this->parseJoin($query, $options['join']),
|
||||
$this->parseWhere($query, $options['where']),
|
||||
$this->parseGroup($query, $options['group']),
|
||||
$this->parseHaving($query, $options['having']),
|
||||
$this->parseOrder($query, $options['order']),
|
||||
$this->parseLimit($query, $one ? '1' : $options['limit']),
|
||||
$this->parseUnion($query, $options['union']),
|
||||
$this->parseLock($query, $options['lock']),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
$this->parseForce($query, $options['force']),
|
||||
],
|
||||
$this->selectSql
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Insert SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function insert(Query $query): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
// 分析并处理数据
|
||||
$data = $this->parseData($query, $options['data']);
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$set = [];
|
||||
foreach ($data as $key => $val) {
|
||||
$set[] = $key . ' = ' . $val;
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%SET%', '%DUPLICATE%', '%COMMENT%'],
|
||||
[
|
||||
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parsePartition($query, $options['partition']),
|
||||
implode(' , ', $set),
|
||||
$this->parseDuplicate($query, $options['duplicate']),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
],
|
||||
$this->insertSql
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成insertall SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $dataSet 数据集
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function insertAll(Query $query, array $dataSet): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
$bind = $query->getFieldsBindType();
|
||||
|
||||
// 获取合法的字段
|
||||
if (empty($options['field']) || '*' == $options['field']) {
|
||||
$allowFields = array_keys($bind);
|
||||
} else {
|
||||
$allowFields = $options['field'];
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($dataSet as $data) {
|
||||
$data = $this->parseData($query, $data, $allowFields, $bind);
|
||||
|
||||
$values[] = '( ' . implode(',', array_values($data)) . ' )';
|
||||
|
||||
if (!isset($insertFields)) {
|
||||
$insertFields = array_keys($data);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($insertFields as $field) {
|
||||
$fields[] = $this->parseKey($query, $field);
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'],
|
||||
[
|
||||
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parsePartition($query, $options['partition']),
|
||||
implode(' , ', $fields),
|
||||
implode(' , ', $values),
|
||||
$this->parseDuplicate($query, $options['duplicate']),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
],
|
||||
$this->insertAllSql
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成insertall SQL
|
||||
* @access public
|
||||
* @param Query $query 查询对象
|
||||
* @param array $keys 键值
|
||||
* @param array $values 数据
|
||||
* @return string
|
||||
*/
|
||||
public function insertAllByKeys(Query $query, array $keys, array $datas): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
$bind = $query->getFieldsBindType();
|
||||
$fields = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($keys as $field) {
|
||||
$fields[] = $this->parseKey($query, $field);
|
||||
}
|
||||
|
||||
foreach ($datas as $data) {
|
||||
foreach ($data as $key => &$val) {
|
||||
if (!$query->isAutoBind()) {
|
||||
$val = PDO::PARAM_STR == $bind[$keys[$key]] ? '\'' . $val . '\'' : $val;
|
||||
} else {
|
||||
$val = $this->parseDataBind($query, $keys[$key], $val, $bind);
|
||||
}
|
||||
}
|
||||
$values[] = '( ' . implode(',', $data) . ' )';
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'],
|
||||
[
|
||||
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parsePartition($query, $options['partition']),
|
||||
implode(' , ', $fields),
|
||||
implode(' , ', $values),
|
||||
$this->parseDuplicate($query, $options['duplicate']),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
],
|
||||
$this->insertAllSql
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成update SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function update(Query $query): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
$data = $this->parseData($query, $options['data']);
|
||||
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$set = [];
|
||||
foreach ($data as $key => $val) {
|
||||
$set[] = (str_contains($key, '->') ? strstr($key, '->', true) : $key) . ' = ' . $val;
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
['%TABLE%', '%PARTITION%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
|
||||
[
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parsePartition($query, $options['partition']),
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
implode(' , ', $set),
|
||||
$this->parseJoin($query, $options['join']),
|
||||
$this->parseWhere($query, $options['where']),
|
||||
$this->parseOrder($query, $options['order']),
|
||||
$this->parseLimit($query, $options['limit']),
|
||||
$this->parseLock($query, $options['lock']),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
],
|
||||
$this->updateSql
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成delete SQL.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function delete(Query $query): string
|
||||
{
|
||||
$options = $query->getOptions();
|
||||
|
||||
return str_replace(
|
||||
['%TABLE%', '%PARTITION%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
|
||||
[
|
||||
$this->parseTable($query, $options['table']),
|
||||
$this->parsePartition($query, $options['partition']),
|
||||
$this->parseExtra($query, $options['extra']),
|
||||
!empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '',
|
||||
$this->parseJoin($query, $options['join']),
|
||||
$this->parseWhere($query, $options['where']),
|
||||
$this->parseOrder($query, $options['order']),
|
||||
$this->parseLimit($query, $options['limit']),
|
||||
$this->parseLock($query, $options['lock']),
|
||||
$this->parseComment($query, $options['comment']),
|
||||
],
|
||||
$this->deleteSql
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 正则查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRegexp(Query $query, string $key, string $exp, $value, string $field): string
|
||||
{
|
||||
if ($value instanceof Raw) {
|
||||
$value = $this->parseRaw($query, $value);
|
||||
}
|
||||
|
||||
return $key . ' ' . $exp . ' ' . $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* FIND_IN_SET 查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseFindInSet(Query $query, string $key, string $exp, $value, string $field): string
|
||||
{
|
||||
if ($value instanceof Raw) {
|
||||
$value = $this->parseRaw($query, $value);
|
||||
}
|
||||
|
||||
return 'FIND_IN_SET(' . $value . ', ' . $key . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和表名处理.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $key 字段名
|
||||
* @param bool $strict 严格检测
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
|
||||
{
|
||||
if (is_int($key)) {
|
||||
return (string) $key;
|
||||
} elseif ($key instanceof Raw) {
|
||||
return $this->parseRaw($query, $key);
|
||||
}
|
||||
|
||||
$key = trim($key);
|
||||
|
||||
if (str_contains($key, '->>') && !str_contains($key, '(')) {
|
||||
// JSON字段支持
|
||||
[$field, $name] = explode('->>', $key, 2);
|
||||
|
||||
return $this->parseKey($query, $field, true) . '->>\'$' . (str_starts_with($name, '[') ? '' : '.') . str_replace('->>', '.', $name) . '\'';
|
||||
} elseif (str_contains($key, '->') && !str_contains($key, '(')) {
|
||||
// JSON字段支持
|
||||
[$field, $name] = explode('->', $key, 2);
|
||||
|
||||
return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (str_starts_with($name, '[') ? '' : '.') . str_replace('->', '.', $name) . '\')';
|
||||
} elseif (str_contains($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
|
||||
[$table, $key] = explode('.', $key, 2);
|
||||
|
||||
$alias = $query->getOptions('alias');
|
||||
|
||||
if ('__TABLE__' == $table) {
|
||||
$table = $query->getOptions('table');
|
||||
$table = is_array($table) ? array_shift($table) : $table;
|
||||
}
|
||||
|
||||
if (isset($alias[$table])) {
|
||||
$table = $alias[$table];
|
||||
}
|
||||
}
|
||||
|
||||
if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
|
||||
throw new Exception('not support data:' . $key);
|
||||
}
|
||||
|
||||
if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
|
||||
$key = '`' . $key . '`';
|
||||
}
|
||||
|
||||
if (isset($table)) {
|
||||
if (str_contains($table, '.')) {
|
||||
$table = str_replace('.', '`.`', $table);
|
||||
}
|
||||
|
||||
$key = '`' . $table . '`.' . $key;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $key
|
||||
* @param string $exp
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @param int $bindType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string
|
||||
{
|
||||
if (str_starts_with($key, "json_extract")) {
|
||||
if ($exp === 'NULL') {
|
||||
return '(' . $key . ' is null OR json_type(' . $key . ') = \'NULL\')';
|
||||
}elseif ($exp === 'NOT NULL'){
|
||||
return '(' . $key . ' is not null AND json_type(' . $key . ') != \'NULL\')';
|
||||
}
|
||||
}
|
||||
|
||||
return parent::parseNull($query, $key, $exp, $value, $field, $bindType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand(Query $query): string
|
||||
{
|
||||
return 'rand()';
|
||||
}
|
||||
|
||||
/**
|
||||
* Partition 分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string|array $partition 分区
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parsePartition(Query $query, $partition): string
|
||||
{
|
||||
if ('' == $partition) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (is_string($partition)) {
|
||||
$partition = explode(',', $partition);
|
||||
}
|
||||
|
||||
return ' PARTITION (' . implode(' , ', $partition) . ') ';
|
||||
}
|
||||
|
||||
/**
|
||||
* ON DUPLICATE KEY UPDATE 分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $duplicate
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDuplicate(Query $query, $duplicate): string
|
||||
{
|
||||
if ('' == $duplicate) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($duplicate instanceof Raw) {
|
||||
return ' ON DUPLICATE KEY UPDATE ' . $this->parseRaw($query, $duplicate) . ' ';
|
||||
}
|
||||
|
||||
if (is_string($duplicate)) {
|
||||
$duplicate = explode(',', $duplicate);
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
foreach ($duplicate as $key => $val) {
|
||||
if (is_numeric($key)) {
|
||||
$val = $this->parseKey($query, $val);
|
||||
$updates[] = $val . ' = VALUES(' . $val . ')';
|
||||
} elseif ($val instanceof Raw) {
|
||||
$updates[] = $this->parseKey($query, $key) . ' = ' . $this->parseRaw($query, $val);
|
||||
} else {
|
||||
$name = $query->bindValue($val, $query->getConnection()->getFieldBindType($key));
|
||||
$updates[] = $this->parseKey($query, $key) . ' = :' . $name;
|
||||
}
|
||||
}
|
||||
|
||||
return ' ON DUPLICATE KEY UPDATE ' . implode(' , ', $updates) . ' ';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\builder;
|
||||
|
||||
use think\db\Builder;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\Raw;
|
||||
|
||||
/**
|
||||
* Oracle数据库驱动.
|
||||
*/
|
||||
class Oracle extends Builder
|
||||
{
|
||||
protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%';
|
||||
|
||||
/**
|
||||
* limit分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $limit
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLimit(Query $query, string $limit): string
|
||||
{
|
||||
$limitStr = '';
|
||||
|
||||
if (!empty($limit)) {
|
||||
$limit = explode(',', $limit);
|
||||
|
||||
if (count($limit) > 1) {
|
||||
$limitStr = '(numrow>' . $limit[0] . ') AND (numrow<=' . ($limit[0] + $limit[1]) . ')';
|
||||
} else {
|
||||
$limitStr = '(numrow>0 AND numrow<=' . $limit[0] . ')';
|
||||
}
|
||||
}
|
||||
|
||||
return $limitStr ? ' WHERE ' . $limitStr : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置锁机制.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param bool|string $lock
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLock(Query $query, bool|string $lock = false): string
|
||||
{
|
||||
if (!$lock) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return ' FOR UPDATE NOWAIT ';
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和表名处理.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string|int|Raw $key
|
||||
* @param bool $strict
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
|
||||
{
|
||||
if (is_int($key)) {
|
||||
return (string) $key;
|
||||
} elseif ($key instanceof Raw) {
|
||||
return $this->parseRaw($query, $key);
|
||||
}
|
||||
|
||||
$key = trim($key);
|
||||
|
||||
if (str_contains($key, '->') && !str_contains($key, '(')) {
|
||||
// JSON字段支持
|
||||
[$field, $name] = explode($key, '->');
|
||||
$key = $field . '."' . $name . '"';
|
||||
} elseif (str_contains($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) {
|
||||
[$table, $key] = explode('.', $key, 2);
|
||||
|
||||
$alias = $query->getOptions('alias');
|
||||
|
||||
if ('__TABLE__' == $table) {
|
||||
$table = $query->getOptions('table');
|
||||
$table = is_array($table) ? array_shift($table) : $table;
|
||||
}
|
||||
|
||||
if (isset($alias[$table])) {
|
||||
$table = $alias[$table];
|
||||
}
|
||||
}
|
||||
|
||||
if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
|
||||
throw new Exception('not support data:' . $key);
|
||||
}
|
||||
|
||||
if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) {
|
||||
$key = '"' . $key . '"';
|
||||
}
|
||||
|
||||
if (isset($table)) {
|
||||
$key = '"' . $table . '".' . $key;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand(Query $query): string
|
||||
{
|
||||
return 'DBMS_RANDOM.value';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\builder;
|
||||
|
||||
use think\db\Builder;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\Raw;
|
||||
|
||||
/**
|
||||
* Pgsql数据库驱动.
|
||||
*/
|
||||
class Pgsql extends Builder
|
||||
{
|
||||
/**
|
||||
* INSERT SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
|
||||
|
||||
/**
|
||||
* INSERT ALL SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
|
||||
|
||||
/**
|
||||
* limit分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $limit
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function parseLimit(Query $query, string $limit): string
|
||||
{
|
||||
$limitStr = '';
|
||||
|
||||
if (!empty($limit)) {
|
||||
$limit = explode(',', $limit);
|
||||
if (count($limit) > 1) {
|
||||
$limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' ';
|
||||
} else {
|
||||
$limitStr .= ' LIMIT ' . $limit[0] . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return $limitStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和表名处理.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string|int|Raw $key 字段名
|
||||
* @param bool $strict 严格检测
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
|
||||
{
|
||||
if (is_int($key)) {
|
||||
return (string) $key;
|
||||
} elseif ($key instanceof Raw) {
|
||||
return $this->parseRaw($query, $key);
|
||||
}
|
||||
|
||||
$key = trim($key);
|
||||
|
||||
if (str_contains($key, '->') && !str_contains($key, '(')) {
|
||||
// JSON字段支持
|
||||
[$field, $name] = explode('->', $key);
|
||||
$key = '"' . $field . '"' . '->>\'' . $name . '\'';
|
||||
} elseif (str_contains($key, '.')) {
|
||||
[$table, $key] = explode('.', $key, 2);
|
||||
|
||||
$alias = $query->getOptions('alias');
|
||||
|
||||
if ('__TABLE__' == $table) {
|
||||
$table = $query->getOptions('table');
|
||||
$table = is_array($table) ? array_shift($table) : $table;
|
||||
}
|
||||
|
||||
if (isset($alias[$table])) {
|
||||
$table = $alias[$table];
|
||||
}
|
||||
|
||||
if ('*' != $key && !preg_match('/[,\"\*\(\).\s]/', $key)) {
|
||||
$key = '"' . $key . '"';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($table)) {
|
||||
$key = $table . '.' . $key;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand(Query $query): string
|
||||
{
|
||||
return 'RANDOM()';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\builder;
|
||||
|
||||
use think\db\Builder;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\Raw;
|
||||
|
||||
/**
|
||||
* Sqlite数据库驱动.
|
||||
*/
|
||||
class Sqlite extends Builder
|
||||
{
|
||||
/**
|
||||
* limit.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $limit
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function parseLimit(Query $query, string $limit): string
|
||||
{
|
||||
$limitStr = '';
|
||||
|
||||
if (!empty($limit)) {
|
||||
$limit = explode(',', $limit);
|
||||
if (count($limit) > 1) {
|
||||
$limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' ';
|
||||
} else {
|
||||
$limitStr .= ' LIMIT ' . $limit[0] . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return $limitStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand(Query $query): string
|
||||
{
|
||||
return 'RANDOM()';
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和表名处理.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string|int|Raw $key 字段名
|
||||
* @param bool $strict 严格检测
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
|
||||
{
|
||||
if (is_int($key)) {
|
||||
return (string) $key;
|
||||
} elseif ($key instanceof Raw) {
|
||||
return $this->parseRaw($query, $key);
|
||||
}
|
||||
|
||||
$key = trim($key);
|
||||
|
||||
if (str_contains($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
|
||||
[$table, $key] = explode('.', $key, 2);
|
||||
|
||||
$alias = $query->getOptions('alias');
|
||||
|
||||
if ('__TABLE__' == $table) {
|
||||
$table = $query->getOptions('table');
|
||||
$table = is_array($table) ? array_shift($table) : $table;
|
||||
}
|
||||
|
||||
if (isset($alias[$table])) {
|
||||
$table = $alias[$table];
|
||||
}
|
||||
}
|
||||
|
||||
if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
|
||||
$key = '`' . $key . '`';
|
||||
}
|
||||
|
||||
if (isset($table)) {
|
||||
$key = '`' . $table . '`.' . $key;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置锁机制.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param bool|string $lock
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLock(Query $query, bool|string $lock = false): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\db\builder;
|
||||
|
||||
use think\db\Builder;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\Raw;
|
||||
|
||||
/**
|
||||
* Sqlsrv数据库驱动.
|
||||
*/
|
||||
class Sqlsrv extends Builder
|
||||
{
|
||||
/**
|
||||
* SELECT SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%';
|
||||
/**
|
||||
* SELECT INSERT SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%';
|
||||
|
||||
/**
|
||||
* UPDATE SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
|
||||
|
||||
/**
|
||||
* DELETE SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
|
||||
|
||||
/**
|
||||
* INSERT SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
|
||||
|
||||
/**
|
||||
* INSERT ALL SQL表达式.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
|
||||
|
||||
/**
|
||||
* order分析.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $order
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseOrder(Query $query, array $order): string
|
||||
{
|
||||
if (empty($order)) {
|
||||
return ' ORDER BY rand()';
|
||||
}
|
||||
|
||||
$array = [];
|
||||
|
||||
foreach ($order as $key => $val) {
|
||||
if ($val instanceof Raw) {
|
||||
$array[] = $this->parseRaw($query, $val);
|
||||
} elseif ('[rand]' == $val) {
|
||||
$array[] = $this->parseRand($query);
|
||||
} else {
|
||||
if (is_numeric($key)) {
|
||||
[$key, $sort] = explode(' ', str_contains($val, ' ') ? $val : $val . ' ');
|
||||
} else {
|
||||
$sort = $val;
|
||||
}
|
||||
|
||||
$sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : '';
|
||||
$array[] = $this->parseKey($query, $key, true) . $sort;
|
||||
}
|
||||
}
|
||||
|
||||
return ' ORDER BY ' . implode(',', $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand(Query $query): string
|
||||
{
|
||||
return 'rand()';
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和表名处理.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string|int|Raw $key 字段名
|
||||
* @param bool $strict 严格检测
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
|
||||
{
|
||||
if (is_int($key)) {
|
||||
return (string) $key;
|
||||
} elseif ($key instanceof Raw) {
|
||||
return $this->parseRaw($query, $key);
|
||||
}
|
||||
|
||||
$key = trim($key);
|
||||
|
||||
if (str_contains($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) {
|
||||
[$table, $key] = explode('.', $key, 2);
|
||||
|
||||
$alias = $query->getOptions('alias');
|
||||
|
||||
if ('__TABLE__' == $table) {
|
||||
$table = $query->getOptions('table');
|
||||
$table = is_array($table) ? array_shift($table) : $table;
|
||||
}
|
||||
|
||||
if (isset($alias[$table])) {
|
||||
$table = $alias[$table];
|
||||
}
|
||||
}
|
||||
|
||||
if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
|
||||
throw new Exception('not support data:' . $key);
|
||||
}
|
||||
|
||||
if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) {
|
||||
$key = '[' . $key . ']';
|
||||
}
|
||||
|
||||
if (isset($table)) {
|
||||
$key = '[' . $table . '].' . $key;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* limit.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param mixed $limit
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLimit(Query $query, string $limit): string
|
||||
{
|
||||
if (empty($limit)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$limit = explode(',', $limit);
|
||||
|
||||
if (count($limit) > 1) {
|
||||
$limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')';
|
||||
} else {
|
||||
$limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ')';
|
||||
}
|
||||
|
||||
return 'WHERE ' . $limitStr;
|
||||
}
|
||||
|
||||
public function selectInsert(Query $query, array $fields, string $table): string
|
||||
{
|
||||
$this->selectSql = $this->selectInsertSql;
|
||||
|
||||
return parent::selectInsert($query, $fields, $table);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\concern;
|
||||
|
||||
use think\db\exception\DbException;
|
||||
use think\db\Raw;
|
||||
|
||||
/**
|
||||
* 聚合查询.
|
||||
*/
|
||||
trait AggregateQuery
|
||||
{
|
||||
/**
|
||||
* 聚合查询.
|
||||
*
|
||||
* @param string $aggregate 聚合方法
|
||||
* @param string|Raw $field 字段名
|
||||
* @param bool $force 强制转为数字类型
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function aggregate(string $aggregate, string|Raw $field, bool $force = false, bool $one = false)
|
||||
{
|
||||
return $this->connection->aggregate($this, $aggregate, $field, $force, $one);
|
||||
}
|
||||
|
||||
/**
|
||||
* COUNT查询.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(string $field = '*'): int
|
||||
{
|
||||
if (!empty($this->options['group'])) {
|
||||
// 支持GROUP
|
||||
|
||||
if (!preg_match('/^[\w\.\*]+$/', $field)) {
|
||||
throw new DbException('not support data:' . $field);
|
||||
}
|
||||
|
||||
$options = $this->getOptions();
|
||||
if (isset($options['cache'])) {
|
||||
$cache = $options['cache'];
|
||||
unset($options['cache']);
|
||||
}
|
||||
|
||||
$subSql = $this->options($options)
|
||||
->field('count(' . $field . ') AS think_count')
|
||||
->bind($this->bind)
|
||||
->buildSql();
|
||||
|
||||
$query = $this->newQuery();
|
||||
if (isset($cache)) {
|
||||
$query->setOption('cache', $cache);
|
||||
}
|
||||
$query->table([$subSql => '_group_count_']);
|
||||
|
||||
$count = $query->aggregate('COUNT', '*');
|
||||
} else {
|
||||
$count = $this->aggregate('COUNT', $field);
|
||||
}
|
||||
|
||||
return (int) $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* SUM查询.
|
||||
*
|
||||
* @param string|Raw $field 字段名
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function sum(string|Raw $field): float
|
||||
{
|
||||
return $this->aggregate('SUM', $field, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* MIN查询.
|
||||
*
|
||||
* @param string|Raw $field 字段名
|
||||
* @param bool $force 强制转为数字类型
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function min(string|Raw $field, bool $force = true)
|
||||
{
|
||||
return $this->aggregate('MIN', $field, $force);
|
||||
}
|
||||
|
||||
/**
|
||||
* MAX查询.
|
||||
*
|
||||
* @param string|Raw $field 字段名
|
||||
* @param bool $force 强制转为数字类型
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function max(string|Raw $field, bool $force = true)
|
||||
{
|
||||
return $this->aggregate('MAX', $field, $force);
|
||||
}
|
||||
|
||||
/**
|
||||
* AVG查询.
|
||||
*
|
||||
* @param string|Raw $field 字段名
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function avg(string|Raw $field): float
|
||||
{
|
||||
return $this->aggregate('AVG', $field, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\concern;
|
||||
|
||||
use think\db\Raw;
|
||||
|
||||
/**
|
||||
* JOIN和VIEW查询.
|
||||
*/
|
||||
trait JoinAndViewQuery
|
||||
{
|
||||
/**
|
||||
* 查询SQL组装 join.
|
||||
*
|
||||
* @param array|string|Raw $join 关联的表名
|
||||
* @param mixed $condition 条件
|
||||
* @param string $type JOIN类型
|
||||
* @param array $bind 参数绑定
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function join(array|string|Raw $join, string $condition = null, string $type = 'INNER', array $bind = [])
|
||||
{
|
||||
$table = $this->getJoinTable($join);
|
||||
|
||||
if (!empty($bind) && $condition) {
|
||||
$this->bindParams($condition, $bind);
|
||||
}
|
||||
|
||||
$this->options['join'][] = [$table, strtoupper($type), $condition];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* LEFT JOIN.
|
||||
*
|
||||
* @param array|string|Raw $join 关联的表名
|
||||
* @param mixed $condition 条件
|
||||
* @param array $bind 参数绑定
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function leftJoin(array|string|Raw $join, string $condition = null, array $bind = [])
|
||||
{
|
||||
return $this->join($join, $condition, 'LEFT', $bind);
|
||||
}
|
||||
|
||||
/**
|
||||
* RIGHT JOIN.
|
||||
*
|
||||
* @param array|string|Raw $join 关联的表名
|
||||
* @param mixed $condition 条件
|
||||
* @param array $bind 参数绑定
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function rightJoin(array|string|Raw $join, string $condition = null, array $bind = [])
|
||||
{
|
||||
return $this->join($join, $condition, 'RIGHT', $bind);
|
||||
}
|
||||
|
||||
/**
|
||||
* FULL JOIN.
|
||||
*
|
||||
* @param array|string|Raw $join 关联的表名
|
||||
* @param mixed $condition 条件
|
||||
* @param array $bind 参数绑定
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function fullJoin(array|string|Raw $join, string $condition = null, array $bind = [])
|
||||
{
|
||||
return $this->join($join, $condition, 'FULL');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Join表名及别名 支持
|
||||
* ['prefix_table或者子查询'=>'alias'] 'table alias'.
|
||||
*
|
||||
* @param array|string|Raw $join JION表名
|
||||
* @param string $alias 别名
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
protected function getJoinTable(array|string|Raw $join, string &$alias = null)
|
||||
{
|
||||
if (is_array($join)) {
|
||||
$table = $join;
|
||||
$alias = array_shift($join);
|
||||
|
||||
return $table;
|
||||
} elseif ($join instanceof Raw) {
|
||||
return $join;
|
||||
}
|
||||
|
||||
$join = trim($join);
|
||||
|
||||
if (str_contains($join, '(')) {
|
||||
// 使用子查询
|
||||
$table = $join;
|
||||
} else {
|
||||
// 使用别名
|
||||
if (str_contains($join, ' ')) {
|
||||
// 使用别名
|
||||
[$table, $alias] = explode(' ', $join);
|
||||
} else {
|
||||
$table = $join;
|
||||
if (!str_contains($join, '.')) {
|
||||
$alias = $join;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->prefix && !str_contains($table, '.') && !str_starts_with($table, $this->prefix)) {
|
||||
$table = $this->getTable($table);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($alias) && $table != $alias) {
|
||||
$table = [$table => $alias];
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定JOIN查询字段.
|
||||
*
|
||||
* @param array|string|Raw $join 数据表
|
||||
* @param string|array|bool $field 查询字段
|
||||
* @param string $on JOIN条件
|
||||
* @param string $type JOIN类型
|
||||
* @param array $bind 参数绑定
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function view(array|string|Raw $join, string|array|bool $field = true, string $on = null, string $type = 'INNER', array $bind = [])
|
||||
{
|
||||
$this->options['view'] = true;
|
||||
|
||||
$fields = [];
|
||||
$table = $this->getJoinTable($join, $alias);
|
||||
|
||||
if (true === $field) {
|
||||
$fields = $alias . '.*';
|
||||
} else {
|
||||
if (is_string($field)) {
|
||||
$field = explode(',', $field);
|
||||
}
|
||||
|
||||
foreach ($field as $key => $val) {
|
||||
if (is_numeric($key)) {
|
||||
$fields[] = $alias . '.' . $val;
|
||||
|
||||
$this->options['map'][$val] = $alias . '.' . $val;
|
||||
} else {
|
||||
if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
|
||||
$name = $key;
|
||||
} else {
|
||||
$name = $alias . '.' . $key;
|
||||
}
|
||||
|
||||
$fields[] = $name . ' AS ' . $val;
|
||||
|
||||
$this->options['map'][$val] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->field($fields);
|
||||
|
||||
if ($on) {
|
||||
$this->join($table, $on, $type, $bind);
|
||||
} else {
|
||||
$this->table($table);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图查询处理.
|
||||
*
|
||||
* @param array $options 查询参数
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseView(array &$options): void
|
||||
{
|
||||
foreach (['AND', 'OR'] as $logic) {
|
||||
if (isset($options['where'][$logic])) {
|
||||
foreach ($options['where'][$logic] as $key => $val) {
|
||||
if (array_key_exists($key, $options['map'])) {
|
||||
array_shift($val);
|
||||
array_unshift($val, $options['map'][$key]);
|
||||
$options['where'][$logic][$options['map'][$key]] = $val;
|
||||
unset($options['where'][$logic][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['order'])) {
|
||||
// 视图查询排序处理
|
||||
foreach ($options['order'] as $key => $val) {
|
||||
if (is_numeric($key) && is_string($val)) {
|
||||
if (str_contains($val, ' ')) {
|
||||
[$field, $sort] = explode(' ', $val);
|
||||
if (array_key_exists($field, $options['map'])) {
|
||||
$options['order'][$options['map'][$field]] = $sort;
|
||||
unset($options['order'][$key]);
|
||||
}
|
||||
} elseif (array_key_exists($val, $options['map'])) {
|
||||
$options['order'][$options['map'][$val]] = 'asc';
|
||||
unset($options['order'][$key]);
|
||||
}
|
||||
} elseif (array_key_exists($key, $options['map'])) {
|
||||
$options['order'][$options['map'][$key]] = $val;
|
||||
unset($options['order'][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,641 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\concern;
|
||||
|
||||
use Closure;
|
||||
use think\helper\Str;
|
||||
use think\Model;
|
||||
use think\model\Collection as ModelCollection;
|
||||
|
||||
/**
|
||||
* 模型及关联查询.
|
||||
*/
|
||||
trait ModelRelationQuery
|
||||
{
|
||||
/**
|
||||
* 当前模型对象
|
||||
*
|
||||
* @var Model
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* 指定模型.
|
||||
*
|
||||
* @param Model $model 模型对象实例
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function model(Model $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的模型对象
|
||||
*
|
||||
* @return Model|null
|
||||
*/
|
||||
public function getModel()
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要隐藏的输出属性.
|
||||
*
|
||||
* @param array $hidden 属性列表
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function hidden(array $hidden = [])
|
||||
{
|
||||
$this->options['hidden'] = $hidden;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要输出的属性.
|
||||
*
|
||||
* @param array $visible
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function visible(array $visible = [])
|
||||
{
|
||||
$this->options['visible'] = $visible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要附加的输出属性.
|
||||
*
|
||||
* @param array $append 属性列表
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function append(array $append = [])
|
||||
{
|
||||
$this->options['append'] = $append;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加查询范围.
|
||||
*
|
||||
* @param array|string|Closure $scope 查询范围定义
|
||||
* @param array $args 参数
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function scope($scope, ...$args)
|
||||
{
|
||||
// 查询范围的第一个参数始终是当前查询对象
|
||||
array_unshift($args, $this);
|
||||
|
||||
if ($scope instanceof Closure) {
|
||||
call_user_func_array($scope, $args);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_string($scope)) {
|
||||
$scope = explode(',', $scope);
|
||||
}
|
||||
|
||||
if ($this->model) {
|
||||
// 检查模型类的查询范围方法
|
||||
foreach ($scope as $name) {
|
||||
$method = 'scope' . trim($name);
|
||||
|
||||
if (method_exists($this->model, $method)) {
|
||||
call_user_func_array([$this->model, $method], $args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置关联查询.
|
||||
*
|
||||
* @param array $relation 关联名称
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function relation(array $relation)
|
||||
{
|
||||
if (empty($this->model) || empty($relation)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->options['relation'] = $relation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用搜索器条件搜索字段.
|
||||
*
|
||||
* @param string|array $fields 搜索字段
|
||||
* @param mixed $data 搜索数据
|
||||
* @param string $prefix 字段前缀标识
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withSearch($fields, $data = [], string $prefix = '')
|
||||
{
|
||||
if (is_string($fields)) {
|
||||
$fields = explode(',', $fields);
|
||||
}
|
||||
|
||||
$likeFields = $this->getConfig('match_like_fields') ?: [];
|
||||
|
||||
foreach ($fields as $key => $field) {
|
||||
if ($field instanceof Closure) {
|
||||
$field($this, $data[$key] ?? null, $data, $prefix);
|
||||
} elseif ($this->model) {
|
||||
// 检测搜索器
|
||||
$fieldName = is_numeric($key) ? $field : $key;
|
||||
$method = 'search' . Str::studly($fieldName) . 'Attr';
|
||||
|
||||
if (method_exists($this->model, $method)) {
|
||||
$this->model->$method($this, $data[$field] ?? null, $data, $prefix);
|
||||
} elseif (isset($data[$field])) {
|
||||
$this->where($fieldName, in_array($fieldName, $likeFields) ? 'like' : '=', in_array($fieldName, $likeFields) ? '%' . $data[$field] . '%' : $data[$field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制关联数据的字段 已废弃直接使用field或withoutfield替代.
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @param array|string $field 关联字段限制
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withField($field)
|
||||
{
|
||||
return $this->field($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制关联数据的数量 已废弃直接使用limit替代.
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @param int $limit 关联数量限制
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withLimit(int $limit)
|
||||
{
|
||||
return $this->limit($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置关联数据不存在的时候默认值
|
||||
*
|
||||
* @param mixed $data 默认值
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withDefault($data = null)
|
||||
{
|
||||
$this->options['default_model'] = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据字段获取器.
|
||||
*
|
||||
* @param string|array $name 字段名
|
||||
* @param callable $callback 闭包获取器
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withAttr(string|array $name, callable $callback = null)
|
||||
{
|
||||
if (is_array($name)) {
|
||||
foreach ($name as $key => $val) {
|
||||
$this->withAttr($key, $val);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->options['with_attr'][$name] = $callback;
|
||||
|
||||
if (str_contains($name, '.')) {
|
||||
[$relation, $field] = explode('.', $name);
|
||||
|
||||
if (!empty($this->options['json']) && in_array($relation, $this->options['json'])) {
|
||||
} else {
|
||||
$this->options['with_relation_attr'][$relation][$field] = $callback;
|
||||
unset($this->options['with_attr'][$name]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联预载入 In方式.
|
||||
*
|
||||
* @param array|string $with 关联方法名称
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function with(array|string $with)
|
||||
{
|
||||
if (empty($this->model) || empty($with)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->options['with'] = (array) $with;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联预载入 JOIN方式.
|
||||
*
|
||||
* @param array|string $with 关联方法名
|
||||
* @param string $joinType JOIN方式
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withJoin(array|string $with, string $joinType = '')
|
||||
{
|
||||
if (empty($this->model) || empty($with)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$with = (array) $with;
|
||||
$first = true;
|
||||
|
||||
foreach ($with as $key => $relation) {
|
||||
$closure = null;
|
||||
$field = true;
|
||||
|
||||
if ($relation instanceof Closure) {
|
||||
// 支持闭包查询过滤关联条件
|
||||
$closure = $relation;
|
||||
$relation = $key;
|
||||
} elseif (is_array($relation)) {
|
||||
$field = $relation;
|
||||
$relation = $key;
|
||||
} elseif (is_string($relation) && str_contains($relation, '.')) {
|
||||
$relation = strstr($relation, '.', true);
|
||||
}
|
||||
|
||||
$result = $this->model->eagerly($this, $relation, $field, $joinType, $closure, $first);
|
||||
|
||||
if (!$result) {
|
||||
unset($with[$key]);
|
||||
} else {
|
||||
$first = false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->via();
|
||||
$this->options['with_join'] = $with;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param array|string $relations 关联方法名
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param bool $subQuery 是否使用子查询
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function withAggregate(string|array $relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
|
||||
{
|
||||
if (empty($this->model)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!$subQuery) {
|
||||
$this->options['with_aggregate'][] = [(array) $relations, $aggregate, $field];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!isset($this->options['field'])) {
|
||||
$this->field('*');
|
||||
}
|
||||
|
||||
$this->model->relationCount($this, (array) $relations, $aggregate, $field, true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联缓存.
|
||||
*
|
||||
* @param string|array|bool $relation 关联方法名
|
||||
* @param mixed $key 缓存key
|
||||
* @param int|\DateTime $expire 缓存有效期
|
||||
* @param string $tag 缓存标签
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withCache(string|array|bool $relation = true, $key = true, $expire = null, string $tag = null)
|
||||
{
|
||||
if (empty($this->model)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (false === $relation || false === $key || !$this->getConnection()->getCache()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) {
|
||||
$expire = $key;
|
||||
$key = true;
|
||||
}
|
||||
|
||||
if (true === $relation || is_numeric($relation)) {
|
||||
$this->options['with_cache'] = $relation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$relations = (array) $relation;
|
||||
foreach ($relations as $name => $relation) {
|
||||
if (!is_numeric($name)) {
|
||||
$this->options['with_cache'][$name] = is_array($relation) ? $relation : [$key, $relation, $tag];
|
||||
} else {
|
||||
$this->options['with_cache'][$relation] = [$key, $expire, $tag];
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param string|array $relation 关联方法名
|
||||
* @param bool $subQuery 是否使用子查询
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withCount(string|array $relation, bool $subQuery = true)
|
||||
{
|
||||
return $this->withAggregate($relation, 'count', '*', $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计Sum.
|
||||
*
|
||||
* @param string|array $relation 关联方法名
|
||||
* @param string $field 字段
|
||||
* @param bool $subQuery 是否使用子查询
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withSum(string|array $relation, string $field, bool $subQuery = true)
|
||||
{
|
||||
return $this->withAggregate($relation, 'sum', $field, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计Max.
|
||||
*
|
||||
* @param string|array $relation 关联方法名
|
||||
* @param string $field 字段
|
||||
* @param bool $subQuery 是否使用子查询
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withMax(string|array $relation, string $field, bool $subQuery = true)
|
||||
{
|
||||
return $this->withAggregate($relation, 'max', $field, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计Min.
|
||||
*
|
||||
* @param string|array $relation 关联方法名
|
||||
* @param string $field 字段
|
||||
* @param bool $subQuery 是否使用子查询
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withMin(string|array $relation, string $field, bool $subQuery = true)
|
||||
{
|
||||
return $this->withAggregate($relation, 'min', $field, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计Avg.
|
||||
*
|
||||
* @param string|array $relation 关联方法名
|
||||
* @param string $field 字段
|
||||
* @param bool $subQuery 是否使用子查询
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withAvg(string|array $relation, string $field, bool $subQuery = true)
|
||||
{
|
||||
return $this->withAggregate($relation, 'avg', $field, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string $relation 关联方法名
|
||||
* @param mixed $operator 比较操作符
|
||||
* @param int $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '')
|
||||
{
|
||||
return $this->model->has($relation, $operator, $count, $id, $joinType, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string $relation 关联方法名
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '')
|
||||
{
|
||||
return $this->model->hasWhere($relation, $where, $fields, $joinType, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON字段数据转换.
|
||||
*
|
||||
* @param array $result 查询数据
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function jsonModelResult(array &$result): void
|
||||
{
|
||||
$withAttr = $this->options['with_attr'];
|
||||
foreach ($this->options['json'] as $name) {
|
||||
if (!isset($result[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$jsonData = json_decode($result[$name], true);
|
||||
|
||||
if (isset($withAttr[$name])) {
|
||||
foreach ($withAttr[$name] as $key => $closure) {
|
||||
$jsonData[$key] = $closure($jsonData[$key] ?? null, $jsonData);
|
||||
}
|
||||
}
|
||||
|
||||
$result[$name] = !$this->options['json_assoc'] ? (object) $jsonData : $jsonData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数据转换为模型数据集对象
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
*
|
||||
* @return ModelCollection
|
||||
*/
|
||||
protected function resultSetToModelCollection(array $resultSet): ModelCollection
|
||||
{
|
||||
if (empty($resultSet)) {
|
||||
return $this->model->toCollection();
|
||||
}
|
||||
|
||||
$this->options['is_resultSet'] = true;
|
||||
|
||||
foreach ($resultSet as $key => &$result) {
|
||||
// 数据转换为模型对象
|
||||
$this->resultToModel($result);
|
||||
}
|
||||
|
||||
foreach (['with', 'with_join'] as $with) {
|
||||
// 关联预载入
|
||||
if (!empty($this->options[$with])) {
|
||||
$result->eagerlyResultSet(
|
||||
$resultSet,
|
||||
$this->options[$with],
|
||||
$this->options['with_relation_attr'],
|
||||
'with_join' == $with,
|
||||
$this->options['with_cache'] ?? false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 模型数据集转换
|
||||
return $this->model->toCollection($resultSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数据转换为模型对象
|
||||
*
|
||||
* @param array $result 查询数据
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function resultToModel(array &$result): void
|
||||
{
|
||||
// JSON数据处理
|
||||
if (!empty($this->options['json'])) {
|
||||
$this->jsonModelResult($result);
|
||||
}
|
||||
|
||||
// 实时读取延迟数据
|
||||
if (!empty($this->options['lazy_fields'])) {
|
||||
$id = $this->getKey($result);
|
||||
foreach ($this->options['lazy_fields'] as $field) {
|
||||
$result[$field] += $this->getLazyFieldValue($field, $id);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->model->newInstance(
|
||||
$result,
|
||||
!empty($this->options['is_resultSet']) ? null : $this->getModelUpdateCondition($this->options),
|
||||
$this->options
|
||||
);
|
||||
|
||||
// 模型数据处理
|
||||
foreach ($this->options['filter'] as $filter) {
|
||||
call_user_func_array($filter, [$result, $this->options]);
|
||||
}
|
||||
|
||||
// 关联查询
|
||||
if (!empty($this->options['relation'])) {
|
||||
$result->relationQuery($this->options['relation'], $this->options['with_relation_attr']);
|
||||
}
|
||||
|
||||
// 关联预载入查询
|
||||
if (empty($this->options['is_resultSet'])) {
|
||||
foreach (['with', 'with_join'] as $with) {
|
||||
if (!empty($this->options[$with])) {
|
||||
$result->eagerlyResult(
|
||||
$this->options[$with],
|
||||
$this->options['with_relation_attr'],
|
||||
'with_join' == $with,
|
||||
$this->options['with_cache'] ?? false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关联统计查询
|
||||
if (!empty($this->options['with_aggregate'])) {
|
||||
foreach ($this->options['with_aggregate'] as $val) {
|
||||
$result->relationCount($this, $val[0], $val[1], $val[2], false);
|
||||
}
|
||||
}
|
||||
|
||||
// 动态获取器
|
||||
if (!empty($this->options['with_attr'])) {
|
||||
$result->withAttr($this->options['with_attr']);
|
||||
}
|
||||
|
||||
foreach (['hidden', 'visible', 'append'] as $name) {
|
||||
if (!empty($this->options[$name])) {
|
||||
$result->$name($this->options[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新原始数据
|
||||
$result->refreshOrigin();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\concern;
|
||||
|
||||
use think\db\Connection;
|
||||
|
||||
/**
|
||||
* 参数绑定支持
|
||||
*/
|
||||
trait ParamsBind
|
||||
{
|
||||
/**
|
||||
* 当前参数绑定.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bind = [];
|
||||
|
||||
/**
|
||||
* 批量参数绑定.
|
||||
*
|
||||
* @param array $value 绑定变量值
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function bind(array $value)
|
||||
{
|
||||
$this->bind = array_merge($this->bind, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个参数绑定.
|
||||
*
|
||||
* @param mixed $value 绑定变量值
|
||||
* @param int $type 绑定类型
|
||||
* @param string $name 绑定标识
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function bindValue($value, int $type = null, string $name = null)
|
||||
{
|
||||
$name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_';
|
||||
|
||||
$this->bind[$name] = [$value, $type ?: Connection::PARAM_STR];
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测参数是否已经绑定.
|
||||
*
|
||||
* @param string $key 参数名
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isBind(string $key)
|
||||
{
|
||||
return isset($this->bind[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自动参数绑定.
|
||||
*
|
||||
* @param bool $bind 是否自动参数绑定
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function autoBind(bool $bind)
|
||||
{
|
||||
$this->options['auto_bind'] = $bind;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否开启自动参数绑定.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAutoBind(): bool
|
||||
{
|
||||
$autoBind = $this->getConfig('auto_param_bind');
|
||||
if (null !== $this->getOptions('auto_bind')) {
|
||||
$autoBind = $this->getOptions('auto_bind');
|
||||
}
|
||||
|
||||
return (bool) $autoBind;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数绑定.
|
||||
*
|
||||
* @param string $sql 绑定的sql表达式
|
||||
* @param array $bind 参数绑定
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function bindParams(string &$sql, array $bind = []): void
|
||||
{
|
||||
foreach ($bind as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$name = $this->bindValue($value[0], $value[1], $value[2] ?? null);
|
||||
} else {
|
||||
$name = $this->bindValue($value);
|
||||
}
|
||||
|
||||
if (is_numeric($key)) {
|
||||
$sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
|
||||
} else {
|
||||
$sql = str_replace(':' . $key, ':' . $name, $sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取绑定的参数 并清空.
|
||||
*
|
||||
* @param bool $clear 是否清空绑定数据
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getBind(bool $clear = true): array
|
||||
{
|
||||
$bind = $this->bind;
|
||||
if ($clear) {
|
||||
$this->bind = [];
|
||||
}
|
||||
|
||||
return $bind;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\concern;
|
||||
|
||||
use Closure;
|
||||
use think\Collection;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\db\Query;
|
||||
use think\helper\Str;
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 查询数据处理.
|
||||
*/
|
||||
trait ResultOperation
|
||||
{
|
||||
/**
|
||||
* 设置数据处理(支持模型).
|
||||
*
|
||||
* @param callable $filter 数据处理Callable
|
||||
* @param string $index 索引(唯一)
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filter(callable $filter, string $index = null)
|
||||
{
|
||||
if ($index) {
|
||||
$this->options['filter'][$index] = $filter;
|
||||
} else {
|
||||
$this->options['filter'][] = $filter;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否允许返回空数据(或空模型).
|
||||
*
|
||||
* @param bool $allowEmpty 是否允许为空
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function allowEmpty(bool $allowEmpty = true)
|
||||
{
|
||||
$this->options['allow_empty'] = $allowEmpty;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置查询数据不存在是否抛出异常.
|
||||
*
|
||||
* @param bool $fail 数据不存在是否抛出异常
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function failException(bool $fail = true)
|
||||
{
|
||||
$this->options['fail'] = $fail;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据.
|
||||
*
|
||||
* @param array $result 查询数据
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function result(array &$result): void
|
||||
{
|
||||
// JSON数据处理
|
||||
if (!empty($this->options['json'])) {
|
||||
$this->jsonResult($result);
|
||||
}
|
||||
|
||||
// 实时读取延迟数据
|
||||
if (!empty($this->options['lazy_fields'])) {
|
||||
$id = $this->getKey($result);
|
||||
foreach ($this->options['lazy_fields'] as $field) {
|
||||
$result[$field] += $this->getLazyFieldValue($field, $id);
|
||||
}
|
||||
}
|
||||
|
||||
// 查询数据处理
|
||||
foreach ($this->options['filter'] as $filter) {
|
||||
$result = call_user_func_array($filter, [$result, $this->options]);
|
||||
}
|
||||
|
||||
// 获取器
|
||||
if (!empty($this->options['with_attr'])) {
|
||||
$this->getResultAttr($result, $this->options['with_attr']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据集.
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param bool $toCollection 是否转为对象
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function resultSet(array &$resultSet, bool $toCollection = true): void
|
||||
{
|
||||
foreach ($resultSet as &$result) {
|
||||
$this->result($result);
|
||||
}
|
||||
|
||||
// 返回Collection对象
|
||||
if ($toCollection) {
|
||||
$resultSet = new Collection($resultSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用获取器处理数据.
|
||||
*
|
||||
* @param array $result 查询数据
|
||||
* @param array $withAttr 字段获取器
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function getResultAttr(array &$result, array $withAttr = []): void
|
||||
{
|
||||
foreach ($withAttr as $name => $closure) {
|
||||
$name = Str::snake($name);
|
||||
|
||||
if (str_contains($name, '.')) {
|
||||
// 支持JSON字段 获取器定义
|
||||
[$key, $field] = explode('.', $name);
|
||||
|
||||
if (isset($result[$key])) {
|
||||
$result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
|
||||
}
|
||||
} else {
|
||||
$result[$name] = $closure($result[$name] ?? null, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理空数据.
|
||||
*
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
* @throws DataNotFoundException
|
||||
*
|
||||
* @return array|Model|null|static
|
||||
*/
|
||||
protected function resultToEmpty()
|
||||
{
|
||||
if (!empty($this->options['fail'])) {
|
||||
$this->throwNotFound();
|
||||
} elseif (!empty($this->options['allow_empty'])) {
|
||||
return !empty($this->model) ? $this->model->newInstance() : [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找单条记录 不存在返回空数据(或者空模型).
|
||||
*
|
||||
* @param mixed $data 数据
|
||||
*
|
||||
* @return array|Model|static|mixed
|
||||
*/
|
||||
public function findOrEmpty($data = null)
|
||||
{
|
||||
return $this->allowEmpty(true)->find($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON字段数据转换.
|
||||
*
|
||||
* @param array $result 查询数据
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function jsonResult(array &$result): void
|
||||
{
|
||||
foreach ($this->options['json'] as $name) {
|
||||
if (!isset($result[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$name] = json_decode($result[$name], true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询失败 抛出异常.
|
||||
*
|
||||
* @throws ModelNotFoundException
|
||||
* @throws DataNotFoundException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function throwNotFound(): void
|
||||
{
|
||||
if (!empty($this->model)) {
|
||||
$class = get_class($this->model);
|
||||
|
||||
throw new ModelNotFoundException('model data Not Found:'.$class, $class, $this->options);
|
||||
}
|
||||
|
||||
$table = $this->getTable();
|
||||
|
||||
throw new DataNotFoundException('table data not Found:'.$table, $table, $this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找多条记录 如果不存在则抛出异常.
|
||||
*
|
||||
* @param array|string|Query|Closure $data 数据
|
||||
*
|
||||
* @throws ModelNotFoundException
|
||||
* @throws DataNotFoundException
|
||||
*
|
||||
* @return array|Collection|static[]
|
||||
*/
|
||||
public function selectOrFail($data = [])
|
||||
{
|
||||
return $this->failException(true)->select($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找单条记录 如果不存在则抛出异常.
|
||||
*
|
||||
* @param array|string|Query|Closure $data 数据
|
||||
*
|
||||
* @throws ModelNotFoundException
|
||||
* @throws DataNotFoundException
|
||||
*
|
||||
* @return array|Model|static|mixed
|
||||
*/
|
||||
public function findOrFail($data = null)
|
||||
{
|
||||
return $this->failException(true)->find($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\concern;
|
||||
|
||||
/**
|
||||
* 数据字段信息.
|
||||
*/
|
||||
trait TableFieldInfo
|
||||
{
|
||||
/**
|
||||
* 获取数据表字段信息.
|
||||
*
|
||||
* @param string $tableName 数据表名
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTableFields(string $tableName = ''): array
|
||||
{
|
||||
if ('' == $tableName) {
|
||||
$tableName = $this->getTable();
|
||||
}
|
||||
|
||||
return $this->connection->getTableFields($tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详细字段类型信息.
|
||||
*
|
||||
* @param string $tableName 数据表名称
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFields(string $tableName = ''): array
|
||||
{
|
||||
return $this->connection->getFields($tableName ?: $this->getTable());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段类型信息.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFieldsType(): array
|
||||
{
|
||||
if (!empty($this->options['field_type'])) {
|
||||
return $this->options['field_type'];
|
||||
}
|
||||
|
||||
return $this->connection->getFieldsType($this->getTable());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段类型信息.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFieldType(string $field)
|
||||
{
|
||||
$fieldType = $this->getFieldsType();
|
||||
|
||||
return $fieldType[$field] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段类型信息.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFieldsBindType(): array
|
||||
{
|
||||
$fieldType = $this->getFieldsType();
|
||||
|
||||
return array_map([$this->connection, 'getFieldBindType'], $fieldType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段类型信息.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getFieldBindType(string $field): int
|
||||
{
|
||||
$fieldType = $this->getFieldType($field);
|
||||
|
||||
return $this->connection->getFieldBindType($fieldType ?: '');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\concern;
|
||||
|
||||
/**
|
||||
* 时间查询支持
|
||||
*/
|
||||
trait TimeFieldQuery
|
||||
{
|
||||
/**
|
||||
* 日期查询表达式.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $timeRule = [
|
||||
'today' => ['today', 'tomorrow -1second'],
|
||||
'yesterday' => ['yesterday', 'today -1second'],
|
||||
'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'],
|
||||
'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'],
|
||||
'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'],
|
||||
'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'],
|
||||
'year' => ['this year 1/1', 'next year 1/1 -1second'],
|
||||
'last year' => ['last year 1/1', 'this year 1/1 -1second'],
|
||||
];
|
||||
|
||||
/**
|
||||
* 添加日期或者时间查询规则.
|
||||
*
|
||||
* @param array $rule 时间表达式
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function timeRule(array $rule)
|
||||
{
|
||||
$this->timeRule = array_merge($this->timeRule, $rule);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询日期或者时间.
|
||||
*
|
||||
* @param string $field 日期字段名
|
||||
* @param string $op 比较运算符或者表达式
|
||||
* @param mixed $range 比较范围
|
||||
* @param string $logic AND OR
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereTime(string $field, string $op, $range = null, string $logic = 'AND')
|
||||
{
|
||||
if (is_null($range)) {
|
||||
$range = $this->timeRule[$op] ?? $op;
|
||||
$op = is_array($range) ? 'between' : '>=';
|
||||
}
|
||||
|
||||
return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询某个时间间隔数据.
|
||||
*
|
||||
* @param string $field 日期字段名
|
||||
* @param string $start 开始时间
|
||||
* @param string $interval 时间间隔单位 day/month/year/week/hour/minute/second
|
||||
* @param int $step 间隔
|
||||
* @param string $logic AND OR
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereTimeInterval(string $field, string $start, string $interval = 'day', int $step = 1, string $logic = 'AND')
|
||||
{
|
||||
$startTime = strtotime($start);
|
||||
$endTime = strtotime(($step > 0 ? '+' : '-') . abs($step) . ' ' . $interval . (abs($step) > 1 ? 's' : ''), $startTime);
|
||||
|
||||
return $this->whereTime($field, 'between', $step > 0 ? [$startTime, $endTime - 1] : [$endTime, $startTime - 1], $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询月数据 whereMonth('time_field', '2018-1').
|
||||
*
|
||||
* @param string $field 日期字段名
|
||||
* @param string $month 月份信息
|
||||
* @param int $step 间隔
|
||||
* @param string $logic AND OR
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereMonth(string $field, string $month = 'this month', int $step = 1, string $logic = 'AND')
|
||||
{
|
||||
if (in_array($month, ['this month', 'last month'])) {
|
||||
$month = date('Y-m', strtotime($month));
|
||||
}
|
||||
|
||||
return $this->whereTimeInterval($field, $month, 'month', $step, $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询周数据 whereWeek('time_field', '2018-1-1') 从2018-1-1开始的一周数据.
|
||||
*
|
||||
* @param string $field 日期字段名
|
||||
* @param string $week 周信息
|
||||
* @param int $step 间隔
|
||||
* @param string $logic AND OR
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereWeek(string $field, string $week = 'this week', int $step = 1, string $logic = 'AND')
|
||||
{
|
||||
if (in_array($week, ['this week', 'last week'])) {
|
||||
$week = date('Y-m-d', strtotime($week));
|
||||
}
|
||||
|
||||
return $this->whereTimeInterval($field, $week, 'week', $step, $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询年数据 whereYear('time_field', '2018').
|
||||
*
|
||||
* @param string $field 日期字段名
|
||||
* @param string $year 年份信息
|
||||
* @param int $step 间隔
|
||||
* @param string $logic AND OR
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereYear(string $field, string $year = 'this year', int $step = 1, string $logic = 'AND')
|
||||
{
|
||||
if (in_array($year, ['this year', 'last year'])) {
|
||||
$year = date('Y', strtotime($year));
|
||||
}
|
||||
|
||||
return $this->whereTimeInterval($field, $year . '-1-1', 'year', $step, $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询日数据 whereDay('time_field', '2018-1-1').
|
||||
*
|
||||
* @param string $field 日期字段名
|
||||
* @param string $day 日期信息
|
||||
* @param int $step 间隔
|
||||
* @param string $logic AND OR
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereDay(string $field, string $day = 'today', int $step = 1, string $logic = 'AND')
|
||||
{
|
||||
if (in_array($day, ['today', 'yesterday'])) {
|
||||
$day = date('Y-m-d', strtotime($day));
|
||||
}
|
||||
|
||||
return $this->whereTimeInterval($field, $day, 'day', $step, $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15').
|
||||
*
|
||||
* @param string $field 日期字段名
|
||||
* @param string|int $startTime 开始时间
|
||||
* @param string|int $endTime 结束时间
|
||||
* @param string $logic AND OR
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND')
|
||||
{
|
||||
return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15').
|
||||
*
|
||||
* @param string $field 日期字段名
|
||||
* @param string|int $startTime 开始时间
|
||||
* @param string|int $endTime 结束时间
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereNotBetweenTime(string $field, $startTime, $endTime)
|
||||
{
|
||||
return $this->whereTime($field, '<', $startTime)
|
||||
->whereTime($field, '>', $endTime, 'OR');
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time').
|
||||
*
|
||||
* @param string $startField 开始时间字段
|
||||
* @param string $endField 结束时间字段
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereBetweenTimeField(string $startField, string $endField)
|
||||
{
|
||||
return $this->whereTime($startField, '<=', time())
|
||||
->whereTime($endField, '>=', time());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time').
|
||||
*
|
||||
* @param string $startField 开始时间字段
|
||||
* @param string $endField 结束时间字段
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereNotBetweenTimeField(string $startField, string $endField)
|
||||
{
|
||||
return $this->whereTime($startField, '>', time())
|
||||
->whereTime($endField, '<', time(), 'OR');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\concern;
|
||||
|
||||
/**
|
||||
* 事务支持
|
||||
*/
|
||||
trait Transaction
|
||||
{
|
||||
/**
|
||||
* 执行数据库Xa事务
|
||||
*
|
||||
* @param callable $callback 数据操作方法回调
|
||||
* @param array $dbs 多个查询对象或者连接对象
|
||||
*
|
||||
* @throws \PDOException
|
||||
* @throws \Exception
|
||||
* @throws \Throwable
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function transactionXa(callable $callback, array $dbs = [])
|
||||
{
|
||||
return $this->connection->transactionXa($callback, $dbs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库事务
|
||||
*
|
||||
* @param callable $callback 数据操作方法回调
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function transaction(callable $callback)
|
||||
{
|
||||
return $this->connection->transaction($callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动事务
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function startTrans(): void
|
||||
{
|
||||
$this->connection->startTrans();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于非自动提交状态下面的查询提交.
|
||||
*
|
||||
* @throws \PDOException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function commit(): void
|
||||
{
|
||||
$this->connection->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务回滚.
|
||||
*
|
||||
* @throws \PDOException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rollback(): void
|
||||
{
|
||||
$this->connection->rollback();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动XA事务
|
||||
*
|
||||
* @param string $xid XA事务id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function startTransXa(string $xid): void
|
||||
{
|
||||
$this->connection->startTransXa($xid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预编译XA事务
|
||||
*
|
||||
* @param string $xid XA事务id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepareXa(string $xid): void
|
||||
{
|
||||
$this->connection->prepareXa($xid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交XA事务
|
||||
*
|
||||
* @param string $xid XA事务id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function commitXa(string $xid): void
|
||||
{
|
||||
$this->connection->commitXa($xid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚XA事务
|
||||
*
|
||||
* @param string $xid XA事务id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rollbackXa(string $xid): void
|
||||
{
|
||||
$this->connection->rollbackXa($xid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\concern;
|
||||
|
||||
use Closure;
|
||||
use think\db\BaseQuery;
|
||||
use think\db\Raw;
|
||||
|
||||
trait WhereQuery
|
||||
{
|
||||
/**
|
||||
* 指定AND查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $op 查询表达式
|
||||
* @param mixed $condition 查询条件
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function where($field, $op = null, $condition = null)
|
||||
{
|
||||
if ($field instanceof $this) {
|
||||
$this->parseQueryWhere($field);
|
||||
|
||||
return $this;
|
||||
} elseif (true === $field || 1 === $field) {
|
||||
$this->options['where']['AND'][] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$pk = $this->getPk();
|
||||
if ((is_null($condition) || '=' == $op) && is_string($pk) && $pk == $field ) {
|
||||
$this->options['key'] = is_null($condition) ? $op : $condition;
|
||||
}
|
||||
|
||||
$param = func_get_args();
|
||||
array_shift($param);
|
||||
|
||||
return $this->parseWhereExp('AND', $field, $op, $condition, $param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析Query对象查询条件.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseQueryWhere(BaseQuery $query): void
|
||||
{
|
||||
$this->options['where'] = $query->getOptions('where') ?? [];
|
||||
$via = $query->getOptions('via');
|
||||
|
||||
if ($via) {
|
||||
foreach ($this->options['where'] as $logic => &$where) {
|
||||
foreach ($where as $key => &$val) {
|
||||
if (is_array($val) && !str_contains($val[0], '.')) {
|
||||
$val[0] = $via . '.' . $val[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->bind($query->getBind(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定OR查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $op 查询表达式
|
||||
* @param mixed $condition 查询条件
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereOr($field, $op = null, $condition = null)
|
||||
{
|
||||
$param = func_get_args();
|
||||
array_shift($param);
|
||||
|
||||
return $this->parseWhereExp('OR', $field, $op, $condition, $param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定XOR查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $op 查询表达式
|
||||
* @param mixed $condition 查询条件
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereXor($field, $op = null, $condition = null)
|
||||
{
|
||||
$param = func_get_args();
|
||||
array_shift($param);
|
||||
|
||||
return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定Null查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereNull(string $field, string $logic = 'AND')
|
||||
{
|
||||
return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定NotNull查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereNotNull(string $field, string $logic = 'AND')
|
||||
{
|
||||
return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定Exists查询条件.
|
||||
*
|
||||
* @param mixed $condition 查询条件
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereExists($condition, string $logic = 'AND')
|
||||
{
|
||||
if (is_string($condition)) {
|
||||
$condition = new Raw($condition);
|
||||
}
|
||||
|
||||
$this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定NotExists查询条件.
|
||||
*
|
||||
* @param mixed $condition 查询条件
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereNotExists($condition, string $logic = 'AND')
|
||||
{
|
||||
if (is_string($condition)) {
|
||||
$condition = new Raw($condition);
|
||||
}
|
||||
|
||||
$this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定In查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $condition 查询条件
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereIn(string $field, $condition, string $logic = 'AND')
|
||||
{
|
||||
return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定NotIn查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $condition 查询条件
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereNotIn(string $field, $condition, string $logic = 'AND')
|
||||
{
|
||||
return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定Like查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $condition 查询条件
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereLike(string $field, $condition, string $logic = 'AND')
|
||||
{
|
||||
return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定NotLike查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $condition 查询条件
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereNotLike(string $field, $condition, string $logic = 'AND')
|
||||
{
|
||||
return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定Between查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $condition 查询条件
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereBetween(string $field, $condition, string $logic = 'AND')
|
||||
{
|
||||
return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定NotBetween查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $condition 查询条件
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereNotBetween(string $field, $condition, string $logic = 'AND')
|
||||
{
|
||||
return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定FIND_IN_SET查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $condition 查询条件
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereFindInSet(string $field, $condition, string $logic = 'AND')
|
||||
{
|
||||
return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个字段.
|
||||
*
|
||||
* @param string $field1 查询字段
|
||||
* @param string $operator 比较操作符
|
||||
* @param string $field2 比较字段
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND')
|
||||
{
|
||||
if (is_null($field2)) {
|
||||
$field2 = $operator;
|
||||
$operator = '=';
|
||||
}
|
||||
|
||||
return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置软删除字段及条件.
|
||||
*
|
||||
* @param string $field 查询字段
|
||||
* @param mixed $condition 查询条件
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function useSoftDelete(string $field, $condition = null)
|
||||
{
|
||||
if ($field) {
|
||||
$this->options['soft_delete'] = [$field, $condition];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定Exp查询条件.
|
||||
*
|
||||
* @param mixed $field 查询字段
|
||||
* @param string $where 查询条件
|
||||
* @param array $bind 参数绑定
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND')
|
||||
{
|
||||
$this->options['where'][$logic][] = [$field, 'EXP', new Raw($where, $bind)];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定字段Raw查询.
|
||||
*
|
||||
* @param string $field 查询字段表达式
|
||||
* @param mixed $op 查询表达式
|
||||
* @param string $condition 查询条件
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND')
|
||||
{
|
||||
if (is_null($condition)) {
|
||||
$condition = $op;
|
||||
$op = '=';
|
||||
}
|
||||
|
||||
$this->options['where'][$logic][] = [new Raw($field), $op, $condition];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定表达式查询条件.
|
||||
*
|
||||
* @param string $where 查询条件
|
||||
* @param array $bind 参数绑定
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereRaw(string $where, array $bind = [], string $logic = 'AND')
|
||||
{
|
||||
$this->options['where'][$logic][] = new Raw($where, $bind);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定表达式查询条件 OR.
|
||||
*
|
||||
* @param string $where 查询条件
|
||||
* @param array $bind 参数绑定
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function whereOrRaw(string $where, array $bind = [])
|
||||
{
|
||||
return $this->whereRaw($where, $bind, 'OR');
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析查询表达式.
|
||||
*
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $op 查询表达式
|
||||
* @param mixed $condition 查询条件
|
||||
* @param array $param 查询参数
|
||||
* @param bool $strict 严格模式
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
|
||||
{
|
||||
$logic = strtoupper($logic);
|
||||
|
||||
if (is_string($field) && !empty($this->options['via']) && !str_contains($field, '.')) {
|
||||
$field = $this->options['via'] . '.' . $field;
|
||||
}
|
||||
|
||||
if ($strict) {
|
||||
// 使用严格模式查询
|
||||
if ('=' == $op) {
|
||||
$where = $this->whereEq($field, $condition);
|
||||
} else {
|
||||
$where = [$field, $op, $condition, $logic];
|
||||
}
|
||||
} elseif (is_array($field)) {
|
||||
// 解析数组批量查询
|
||||
return $this->parseArrayWhereItems($field, $logic);
|
||||
} elseif ($field instanceof Closure) {
|
||||
$where = $field;
|
||||
} elseif (is_string($field)) {
|
||||
if ($condition instanceof Raw) {
|
||||
} elseif (preg_match('/[,=\<\'\"\(\s]/', $field)) {
|
||||
return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
|
||||
} elseif (is_string($op) && strtolower($op) == 'exp' && !is_null($condition)) {
|
||||
$bind = isset($param[2]) && is_array($param[2]) ? $param[2] : [];
|
||||
|
||||
return $this->whereExp($field, $condition, $bind, $logic);
|
||||
}
|
||||
|
||||
$where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
|
||||
}
|
||||
|
||||
if (!empty($where)) {
|
||||
$this->options['where'][$logic][] = $where;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析查询表达式.
|
||||
*
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
* @param mixed $field 查询字段
|
||||
* @param mixed $op 查询表达式
|
||||
* @param mixed $condition 查询条件
|
||||
* @param array $param 查询参数
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array
|
||||
{
|
||||
if (is_array($op)) {
|
||||
// 同一字段多条件查询
|
||||
array_unshift($param, $field);
|
||||
$where = $param;
|
||||
} elseif ($field && is_null($condition)) {
|
||||
if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
|
||||
// null查询
|
||||
$where = [$field, $op, ''];
|
||||
} elseif ('=' === $op || is_null($op)) {
|
||||
$where = [$field, 'NULL', ''];
|
||||
} elseif ('<>' === $op) {
|
||||
$where = [$field, 'NOTNULL', ''];
|
||||
} else {
|
||||
// 字段相等查询
|
||||
$where = $this->whereEq($field, $op);
|
||||
}
|
||||
} elseif (is_string($op) && in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
|
||||
$where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition];
|
||||
} else {
|
||||
$where = $field ? [$field, $op, $condition, $param[2] ?? null] : [];
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相等查询的主键处理.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param mixed $value 字段值
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function whereEq(string $field, $value): array
|
||||
{
|
||||
if ($this->getPk() == $field) {
|
||||
$this->options['key'] = $value;
|
||||
}
|
||||
|
||||
return [$field, '=', $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组批量查询.
|
||||
*
|
||||
* @param array $field 批量查询
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function parseArrayWhereItems(array $field, string $logic)
|
||||
{
|
||||
$where = [];
|
||||
foreach ($field as $key => $val) {
|
||||
if (is_int($key)) {
|
||||
$where[] = $val;
|
||||
} elseif ($val instanceof Raw) {
|
||||
$where[] = [$key, 'exp', $val];
|
||||
} else {
|
||||
$where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($where)) {
|
||||
$this->options['where'][$logic] = isset($this->options['where'][$logic]) ?
|
||||
array_merge($this->options['where'][$logic], $where) : $where;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除某个查询条件.
|
||||
*
|
||||
* @param string $field 查询字段
|
||||
* @param string $logic 查询逻辑 and or xor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeWhereField(string $field, string $logic = 'AND')
|
||||
{
|
||||
$logic = strtoupper($logic);
|
||||
|
||||
if (isset($this->options['where'][$logic])) {
|
||||
foreach ($this->options['where'][$logic] as $key => $val) {
|
||||
if (is_array($val) && $val[0] == $field) {
|
||||
unset($this->options['where'][$logic][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件查询.
|
||||
*
|
||||
* @param mixed $condition 满足条件(支持闭包)
|
||||
* @param Closure|array $query 满足条件后执行的查询表达式(闭包或数组)
|
||||
* @param Closure|array $otherwise 不满足条件后执行
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function when($condition, $query, $otherwise = null)
|
||||
{
|
||||
if ($condition instanceof Closure) {
|
||||
$condition = $condition($this);
|
||||
}
|
||||
|
||||
if ($condition) {
|
||||
if ($query instanceof Closure) {
|
||||
$query($this, $condition);
|
||||
} elseif (is_array($query)) {
|
||||
$this->where($query);
|
||||
}
|
||||
} elseif ($otherwise) {
|
||||
if ($otherwise instanceof Closure) {
|
||||
$otherwise($this, $condition);
|
||||
} elseif (is_array($otherwise)) {
|
||||
$this->where($otherwise);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\connector;
|
||||
|
||||
use PDO;
|
||||
use think\db\PDOConnection;
|
||||
|
||||
/**
|
||||
* mysql数据库驱动.
|
||||
*/
|
||||
class Mysql extends PDOConnection
|
||||
{
|
||||
/**
|
||||
* 解析pdo连接的dsn信息.
|
||||
*
|
||||
* @param array $config 连接信息
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDsn(array $config): string
|
||||
{
|
||||
if (!empty($config['socket'])) {
|
||||
$dsn = 'mysql:unix_socket=' . $config['socket'];
|
||||
} elseif (!empty($config['hostport'])) {
|
||||
$dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport'];
|
||||
} else {
|
||||
$dsn = 'mysql:host=' . $config['hostname'];
|
||||
}
|
||||
$dsn .= ';dbname=' . $config['database'];
|
||||
|
||||
if (!empty($config['charset'])) {
|
||||
$dsn .= ';charset=' . $config['charset'];
|
||||
}
|
||||
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息.
|
||||
*
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFields(string $tableName): array
|
||||
{
|
||||
[$tableName] = explode(' ', $tableName);
|
||||
|
||||
if (!str_contains($tableName, '`')) {
|
||||
if (str_contains($tableName, '.')) {
|
||||
$tableName = str_replace('.', '`.`', $tableName);
|
||||
}
|
||||
$tableName = '`' . $tableName . '`';
|
||||
}
|
||||
|
||||
$sql = 'SHOW FULL COLUMNS FROM ' . $tableName;
|
||||
$pdo = $this->getPDOStatement($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
|
||||
if (!empty($result)) {
|
||||
foreach ($result as $key => $val) {
|
||||
$val = array_change_key_case($val);
|
||||
|
||||
$info[$val['field']] = [
|
||||
'name' => $val['field'],
|
||||
'type' => $val['type'],
|
||||
'notnull' => 'NO' == $val['null'],
|
||||
'default' => $val['default'],
|
||||
'primary' => strtolower($val['key']) == 'pri',
|
||||
'autoinc' => strtolower($val['extra']) == 'auto_increment',
|
||||
'comment' => $val['comment'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fieldCase($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库的表信息.
|
||||
*
|
||||
* @param string $dbName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTables(string $dbName = ''): array
|
||||
{
|
||||
$sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES ';
|
||||
$pdo = $this->getPDOStatement($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
|
||||
foreach ($result as $key => $val) {
|
||||
$info[$key] = current($val);
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
protected function supportSavepoint(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动XA事务
|
||||
*
|
||||
* @param string $xid XA事务id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function startTransXa(string $xid): void
|
||||
{
|
||||
$this->initConnect(true);
|
||||
$this->linkID->exec("XA START '$xid'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 预编译XA事务
|
||||
*
|
||||
* @param string $xid XA事务id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepareXa(string $xid): void
|
||||
{
|
||||
$this->initConnect(true);
|
||||
$this->linkID->exec("XA END '$xid'");
|
||||
$this->linkID->exec("XA PREPARE '$xid'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交XA事务
|
||||
*
|
||||
* @param string $xid XA事务id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function commitXa(string $xid): void
|
||||
{
|
||||
$this->initConnect(true);
|
||||
$this->linkID->exec("XA COMMIT '$xid'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚XA事务
|
||||
*
|
||||
* @param string $xid XA事务id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rollbackXa(string $xid): void
|
||||
{
|
||||
$this->initConnect(true);
|
||||
$this->linkID->exec("XA ROLLBACK '$xid'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\db\connector;
|
||||
|
||||
use PDO;
|
||||
use think\db\BaseQuery;
|
||||
use think\db\PDOConnection;
|
||||
|
||||
/**
|
||||
* Oracle数据库驱动.
|
||||
*/
|
||||
class Oracle extends PDOConnection
|
||||
{
|
||||
/**
|
||||
* 解析pdo连接的dsn信息.
|
||||
*
|
||||
* @param array $config 连接信息
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDsn(array $config): string
|
||||
{
|
||||
$dsn = 'oci:dbname=';
|
||||
|
||||
if (!empty($config['hostname'])) {
|
||||
// Oracle Instant Client
|
||||
$dsn .= '//' . $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/';
|
||||
}
|
||||
|
||||
$dsn .= $config['database'];
|
||||
|
||||
if (!empty($config['charset'])) {
|
||||
$dsn .= ';charset=' . $config['charset'];
|
||||
}
|
||||
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息.
|
||||
*
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFields(string $tableName): array
|
||||
{
|
||||
[$tableName] = explode(' ', $tableName);
|
||||
|
||||
$sql = "select a.column_name,data_type,DECODE (nullable, 'Y', 0, 1) notnull,data_default, DECODE (A .column_name,b.column_name,1,0) pk from all_tab_columns a,(select column_name from all_constraints c, all_cons_columns col where c.constraint_name = col.constraint_name and c.constraint_type = 'P' and c.table_name = '" . $tableName . "' ) b where table_name = '" . $tableName . "' and a.column_name = b.column_name (+)";
|
||||
$pdo = $this->getPDOStatement($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
|
||||
if ($result) {
|
||||
foreach ($result as $key => $val) {
|
||||
$val = array_change_key_case($val);
|
||||
|
||||
$info[$val['column_name']] = [
|
||||
'name' => $val['column_name'],
|
||||
'type' => $val['data_type'],
|
||||
'notnull' => $val['notnull'],
|
||||
'default' => $val['data_default'],
|
||||
'primary' => $val['pk'],
|
||||
'autoinc' => $val['pk'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fieldCase($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库的表信息(暂时实现取得用户表信息).
|
||||
*
|
||||
* @param string $dbName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTables(string $dbName = ''): array
|
||||
{
|
||||
$sql = 'select table_name from all_tables';
|
||||
$pdo = $this->getPDOStatement($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
|
||||
foreach ($result as $key => $val) {
|
||||
$info[$key] = current($val);
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近插入的ID.
|
||||
*
|
||||
* @param BaseQuery $query 查询对象
|
||||
* @param string|null $sequence 自增序列名
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLastInsID(BaseQuery $query, string $sequence = null)
|
||||
{
|
||||
if (!is_null($sequence)) {
|
||||
$pdo = $this->linkID->query("select {$sequence}.currval as id from dual");
|
||||
$result = $pdo->fetchColumn();
|
||||
}
|
||||
|
||||
return $result ?? null;
|
||||
}
|
||||
|
||||
protected function supportSavepoint(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\db\connector;
|
||||
|
||||
use PDO;
|
||||
use think\db\PDOConnection;
|
||||
|
||||
/**
|
||||
* Pgsql数据库驱动.
|
||||
*/
|
||||
class Pgsql extends PDOConnection
|
||||
{
|
||||
/**
|
||||
* 默认PDO连接参数.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $params = [
|
||||
PDO::ATTR_CASE => PDO::CASE_NATURAL,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* 解析pdo连接的dsn信息.
|
||||
*
|
||||
* @param array $config 连接信息
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDsn(array $config): string
|
||||
{
|
||||
$dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname'];
|
||||
|
||||
if (!empty($config['hostport'])) {
|
||||
$dsn .= ';port=' . $config['hostport'];
|
||||
}
|
||||
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息.
|
||||
*
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFields(string $tableName): array
|
||||
{
|
||||
[$tableName] = explode(' ', $tableName);
|
||||
|
||||
$sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');';
|
||||
$pdo = $this->getPDOStatement($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
|
||||
if (!empty($result)) {
|
||||
foreach ($result as $key => $val) {
|
||||
$val = array_change_key_case($val);
|
||||
|
||||
$info[$val['field']] = [
|
||||
'name' => $val['field'],
|
||||
'type' => $val['type'],
|
||||
'notnull' => (bool) ('' !== $val['null']),
|
||||
'default' => $val['default'],
|
||||
'primary' => !empty($val['key']),
|
||||
'autoinc' => str_starts_with((string) $val['extra'], 'nextval('),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fieldCase($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库的表信息.
|
||||
*
|
||||
* @param string $dbName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTables(string $dbName = ''): array
|
||||
{
|
||||
$sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'";
|
||||
$pdo = $this->getPDOStatement($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
|
||||
foreach ($result as $key => $val) {
|
||||
$info[$key] = current($val);
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
protected function supportSavepoint(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\db\connector;
|
||||
|
||||
use PDO;
|
||||
use think\db\PDOConnection;
|
||||
|
||||
/**
|
||||
* Sqlite数据库驱动.
|
||||
*/
|
||||
class Sqlite extends PDOConnection
|
||||
{
|
||||
/**
|
||||
* 解析pdo连接的dsn信息.
|
||||
*
|
||||
* @param array $config 连接信息
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDsn(array $config): string
|
||||
{
|
||||
return 'sqlite:' . $config['database'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息.
|
||||
*
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFields(string $tableName): array
|
||||
{
|
||||
[$tableName] = explode(' ', $tableName);
|
||||
|
||||
$sql = 'PRAGMA table_info( \'' . $tableName . '\' )';
|
||||
$pdo = $this->getPDOStatement($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
|
||||
if (!empty($result)) {
|
||||
foreach ($result as $key => $val) {
|
||||
$val = array_change_key_case($val);
|
||||
|
||||
$info[$val['name']] = [
|
||||
'name' => $val['name'],
|
||||
'type' => $val['type'],
|
||||
'notnull' => 1 === $val['notnull'],
|
||||
'default' => $val['dflt_value'],
|
||||
'primary' => '1' == $val['pk'],
|
||||
'autoinc' => '1' == $val['pk'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fieldCase($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库的表信息.
|
||||
*
|
||||
* @param string $dbName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTables(string $dbName = ''): array
|
||||
{
|
||||
$sql = "SELECT name FROM sqlite_master WHERE type='table' "
|
||||
. 'UNION ALL SELECT name FROM sqlite_temp_master '
|
||||
. "WHERE type='table' ORDER BY name";
|
||||
|
||||
$pdo = $this->getPDOStatement($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
|
||||
foreach ($result as $key => $val) {
|
||||
$info[$key] = current($val);
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
protected function supportSavepoint(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\db\connector;
|
||||
|
||||
use PDO;
|
||||
use think\db\PDOConnection;
|
||||
|
||||
/**
|
||||
* Sqlsrv数据库驱动.
|
||||
*/
|
||||
class Sqlsrv extends PDOConnection
|
||||
{
|
||||
/**
|
||||
* 默认PDO连接参数.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $params = [
|
||||
PDO::ATTR_CASE => PDO::CASE_NATURAL,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* 解析pdo连接的dsn信息.
|
||||
*
|
||||
* @param array $config 连接信息
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDsn(array $config): string
|
||||
{
|
||||
$dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname'];
|
||||
|
||||
if (!empty($config['hostport'])) {
|
||||
$dsn .= ',' . $config['hostport'];
|
||||
}
|
||||
|
||||
if (!empty($config['trust_server_certificate'])) {
|
||||
$dsn .= ';TrustServerCertificate=' . $config['trust_server_certificate'];
|
||||
}
|
||||
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息.
|
||||
*
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFields(string $tableName): array
|
||||
{
|
||||
[$tableName] = explode(' ', $tableName);
|
||||
str_contains($tableName, '.') && $tableName = substr($tableName, strpos($tableName, '.') + 1);
|
||||
|
||||
$sql = "SELECT column_name, data_type, column_default, is_nullable
|
||||
FROM information_schema.tables AS t
|
||||
JOIN information_schema.columns AS c
|
||||
ON t.table_catalog = c.table_catalog
|
||||
AND t.table_schema = c.table_schema
|
||||
AND t.table_name = c.table_name
|
||||
WHERE t.table_name = '$tableName'";
|
||||
$pdo = $this->getPDOStatement($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
|
||||
if (!empty($result)) {
|
||||
foreach ($result as $key => $val) {
|
||||
$val = array_change_key_case($val);
|
||||
|
||||
$info[$val['column_name']] = [
|
||||
'name' => $val['column_name'],
|
||||
'type' => $val['data_type'],
|
||||
'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes
|
||||
'default' => $val['column_default'],
|
||||
'primary' => false,
|
||||
'autoinc' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'";
|
||||
$pdo = $this->linkID->query($sql);
|
||||
$result = $pdo->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result) {
|
||||
$info[$result['column_name']]['primary'] = true;
|
||||
}
|
||||
|
||||
return $this->fieldCase($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息.
|
||||
*
|
||||
* @param string $dbName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTables(string $dbName = ''): array
|
||||
{
|
||||
$sql = "SELECT TABLE_NAME
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_TYPE = 'BASE TABLE'
|
||||
";
|
||||
|
||||
$pdo = $this->getPDOStatement($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
|
||||
foreach ($result as $key => $val) {
|
||||
$info[$key] = current($val);
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS
|
||||
$BODY$
|
||||
DECLARE
|
||||
v_type varchar;
|
||||
BEGIN
|
||||
IF a_type='int8' THEN
|
||||
v_type:='bigint';
|
||||
ELSIF a_type='int4' THEN
|
||||
v_type:='integer';
|
||||
ELSIF a_type='int2' THEN
|
||||
v_type:='smallint';
|
||||
ELSIF a_type='bpchar' THEN
|
||||
v_type:='char';
|
||||
ELSE
|
||||
v_type:=a_type;
|
||||
END IF;
|
||||
RETURN v_type;
|
||||
END;
|
||||
$BODY$
|
||||
LANGUAGE PLPGSQL;
|
||||
|
||||
CREATE TYPE "public"."tablestruct" AS (
|
||||
"fields_key_name" varchar(100),
|
||||
"fields_name" VARCHAR(200),
|
||||
"fields_type" VARCHAR(20),
|
||||
"fields_length" BIGINT,
|
||||
"fields_not_null" VARCHAR(10),
|
||||
"fields_default" VARCHAR(500),
|
||||
"fields_comment" VARCHAR(1000)
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
|
||||
$body$
|
||||
DECLARE
|
||||
v_ret tablestruct;
|
||||
v_oid oid;
|
||||
v_sql varchar;
|
||||
v_rec RECORD;
|
||||
v_key varchar;
|
||||
BEGIN
|
||||
SELECT
|
||||
pg_class.oid INTO v_oid
|
||||
FROM
|
||||
pg_class
|
||||
INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name)
|
||||
WHERE
|
||||
pg_class.relname=a_table_name;
|
||||
IF NOT FOUND THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
v_sql='
|
||||
SELECT
|
||||
pg_attribute.attname AS fields_name,
|
||||
pg_attribute.attnum AS fields_index,
|
||||
pgsql_type(pg_type.typname::varchar) AS fields_type,
|
||||
pg_attribute.atttypmod-4 as fields_length,
|
||||
CASE WHEN pg_attribute.attnotnull THEN ''not null''
|
||||
ELSE ''''
|
||||
END AS fields_not_null,
|
||||
pg_attrdef.adsrc AS fields_default,
|
||||
pg_description.description AS fields_comment
|
||||
FROM
|
||||
pg_attribute
|
||||
INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid
|
||||
INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid
|
||||
LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum
|
||||
LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum
|
||||
WHERE
|
||||
pg_attribute.attnum > 0
|
||||
AND attisdropped <> ''t''
|
||||
AND pg_class.oid = ' || v_oid || '
|
||||
ORDER BY pg_attribute.attnum' ;
|
||||
|
||||
FOR v_rec IN EXECUTE v_sql LOOP
|
||||
v_ret.fields_name=v_rec.fields_name;
|
||||
v_ret.fields_type=v_rec.fields_type;
|
||||
IF v_rec.fields_length > 0 THEN
|
||||
v_ret.fields_length:=v_rec.fields_length;
|
||||
ELSE
|
||||
v_ret.fields_length:=NULL;
|
||||
END IF;
|
||||
v_ret.fields_not_null=v_rec.fields_not_null;
|
||||
v_ret.fields_default=v_rec.fields_default;
|
||||
v_ret.fields_comment=v_rec.fields_comment;
|
||||
SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name;
|
||||
IF FOUND THEN
|
||||
v_ret.fields_key_name=v_key;
|
||||
ELSE
|
||||
v_ret.fields_key_name='';
|
||||
END IF;
|
||||
RETURN NEXT v_ret;
|
||||
END LOOP;
|
||||
RETURN ;
|
||||
END;
|
||||
$body$
|
||||
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
|
||||
|
||||
COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar)
|
||||
IS '获得表信息';
|
||||
|
||||
---重载一个函数
|
||||
CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
|
||||
$body$
|
||||
DECLARE
|
||||
v_ret tablestruct;
|
||||
BEGIN
|
||||
FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP
|
||||
RETURN NEXT v_ret;
|
||||
END LOOP;
|
||||
RETURN;
|
||||
END;
|
||||
$body$
|
||||
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
|
||||
|
||||
COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar)
|
||||
IS '获得表信息';
|
||||
@@ -0,0 +1,117 @@
|
||||
CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS
|
||||
$BODY$
|
||||
DECLARE
|
||||
v_type varchar;
|
||||
BEGIN
|
||||
IF a_type='int8' THEN
|
||||
v_type:='bigint';
|
||||
ELSIF a_type='int4' THEN
|
||||
v_type:='integer';
|
||||
ELSIF a_type='int2' THEN
|
||||
v_type:='smallint';
|
||||
ELSIF a_type='bpchar' THEN
|
||||
v_type:='char';
|
||||
ELSE
|
||||
v_type:=a_type;
|
||||
END IF;
|
||||
RETURN v_type;
|
||||
END;
|
||||
$BODY$
|
||||
LANGUAGE PLPGSQL;
|
||||
|
||||
CREATE TYPE "public"."tablestruct" AS (
|
||||
"fields_key_name" varchar(100),
|
||||
"fields_name" VARCHAR(200),
|
||||
"fields_type" VARCHAR(20),
|
||||
"fields_length" BIGINT,
|
||||
"fields_not_null" VARCHAR(10),
|
||||
"fields_default" VARCHAR(500),
|
||||
"fields_comment" VARCHAR(1000)
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
|
||||
$body$
|
||||
DECLARE
|
||||
v_ret tablestruct;
|
||||
v_oid oid;
|
||||
v_sql varchar;
|
||||
v_rec RECORD;
|
||||
v_key varchar;
|
||||
BEGIN
|
||||
SELECT
|
||||
pg_class.oid INTO v_oid
|
||||
FROM
|
||||
pg_class
|
||||
INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name)
|
||||
WHERE
|
||||
pg_class.relname=a_table_name;
|
||||
IF NOT FOUND THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
v_sql='
|
||||
SELECT
|
||||
pg_attribute.attname AS fields_name,
|
||||
pg_attribute.attnum AS fields_index,
|
||||
pgsql_type(pg_type.typname::varchar) AS fields_type,
|
||||
pg_attribute.atttypmod-4 as fields_length,
|
||||
CASE WHEN pg_attribute.attnotnull THEN ''not null''
|
||||
ELSE ''''
|
||||
END AS fields_not_null,
|
||||
pg_get_expr(pg_attrdef.adbin, pg_attrdef.adrelid) AS fields_default,
|
||||
pg_description.description AS fields_comment
|
||||
FROM
|
||||
pg_attribute
|
||||
INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid
|
||||
INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid
|
||||
LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum
|
||||
LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum
|
||||
WHERE
|
||||
pg_attribute.attnum > 0
|
||||
AND attisdropped <> ''t''
|
||||
AND pg_class.oid = ' || v_oid || '
|
||||
ORDER BY pg_attribute.attnum' ;
|
||||
|
||||
FOR v_rec IN EXECUTE v_sql LOOP
|
||||
v_ret.fields_name=v_rec.fields_name;
|
||||
v_ret.fields_type=v_rec.fields_type;
|
||||
IF v_rec.fields_length > 0 THEN
|
||||
v_ret.fields_length:=v_rec.fields_length;
|
||||
ELSE
|
||||
v_ret.fields_length:=NULL;
|
||||
END IF;
|
||||
v_ret.fields_not_null=v_rec.fields_not_null;
|
||||
v_ret.fields_default=v_rec.fields_default;
|
||||
v_ret.fields_comment=v_rec.fields_comment;
|
||||
SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name;
|
||||
IF FOUND THEN
|
||||
v_ret.fields_key_name=v_key;
|
||||
ELSE
|
||||
v_ret.fields_key_name='';
|
||||
END IF;
|
||||
RETURN NEXT v_ret;
|
||||
END LOOP;
|
||||
RETURN ;
|
||||
END;
|
||||
$body$
|
||||
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
|
||||
|
||||
COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar)
|
||||
IS '获得表信息';
|
||||
|
||||
---重载一个函数
|
||||
CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
|
||||
$body$
|
||||
DECLARE
|
||||
v_ret tablestruct;
|
||||
BEGIN
|
||||
FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP
|
||||
RETURN NEXT v_ret;
|
||||
END LOOP;
|
||||
RETURN;
|
||||
END;
|
||||
$body$
|
||||
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
|
||||
|
||||
COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar)
|
||||
IS '获得表信息';
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\exception;
|
||||
|
||||
/**
|
||||
* PDO参数绑定异常.
|
||||
*/
|
||||
class BindParamException extends DbException
|
||||
{
|
||||
/**
|
||||
* BindParamException constructor.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $config
|
||||
* @param string $sql
|
||||
* @param array $bind
|
||||
* @param int $code
|
||||
*/
|
||||
public function __construct(string $message, array $config, string $sql, array $bind, int $code = 10502)
|
||||
{
|
||||
$this->setData('Bind Param', $bind);
|
||||
parent::__construct($message, $config, $sql, $code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\exception;
|
||||
|
||||
class DataNotFoundException extends DbException
|
||||
{
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* DbException constructor.
|
||||
*
|
||||
* @param string $message
|
||||
* @param string $table
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(string $message, string $table = '', array $config = [])
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->table = $table;
|
||||
|
||||
$this->setData('Database Config', $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据表名.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTable()
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\db\exception;
|
||||
|
||||
/**
|
||||
* Db事件异常.
|
||||
*/
|
||||
class DbEventException extends DbException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\exception;
|
||||
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* Database相关异常处理类.
|
||||
*/
|
||||
class DbException extends Exception
|
||||
{
|
||||
/**
|
||||
* DbException constructor.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $config
|
||||
* @param string $sql
|
||||
* @param int $code
|
||||
*/
|
||||
public function __construct(string $message, array $config = [], string $sql = '', int $code = 10500)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->code = $code;
|
||||
|
||||
$this->setData('Database Status', [
|
||||
'Error Code' => $code,
|
||||
'Error Message' => $message,
|
||||
'Error SQL' => $sql,
|
||||
]);
|
||||
|
||||
unset($config['username'], $config['password']);
|
||||
$this->setData('Database Config', $config);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\exception;
|
||||
|
||||
use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface;
|
||||
|
||||
/**
|
||||
* 非法数据异常.
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements SimpleCacheInvalidArgumentInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\db\exception;
|
||||
|
||||
/**
|
||||
* 模型事件异常.
|
||||
*/
|
||||
class ModelEventException extends DbException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\exception;
|
||||
|
||||
class ModelNotFoundException extends DbException
|
||||
{
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* 构造方法.
|
||||
*
|
||||
* @param string $message
|
||||
* @param string $model
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(string $message, string $model = '', array $config = [])
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->model = $model;
|
||||
|
||||
$this->setData('Database Config', $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型类名.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getModel()
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\db\exception;
|
||||
|
||||
/**
|
||||
* PDO异常处理类
|
||||
* 重新封装了系统的\PDOException类.
|
||||
*/
|
||||
class PDOException extends DbException
|
||||
{
|
||||
/**
|
||||
* PDOException constructor.
|
||||
*
|
||||
* @param \PDOException $exception
|
||||
* @param array $config
|
||||
* @param string $sql
|
||||
* @param int $code
|
||||
*/
|
||||
public function __construct(\PDOException $exception, array $config = [], string $sql = '', int $code = 10501)
|
||||
{
|
||||
$error = $exception->errorInfo;
|
||||
$message = $exception->getMessage();
|
||||
|
||||
if (!empty($error)) {
|
||||
$this->setData('PDO Error Info', [
|
||||
'SQLSTATE' => $error[0],
|
||||
'Driver Error Code' => $error[1] ?? 0,
|
||||
'Driver Error Message' => $error[2] ?? '',
|
||||
]);
|
||||
}
|
||||
|
||||
parent::__construct($message, $config, $sql, $code);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user