This commit is contained in:
2026-07-23 10:37:26 +08:00
parent 090abf48fa
commit 40add876cd
3503 changed files with 838201 additions and 5 deletions
+971
View File
@@ -0,0 +1,971 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 BackedEnum;
use Closure;
use think\db\BaseQuery as Query;
use think\db\exception\DbException as Exception;
use UnitEnum;
/**
* 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->getOption('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
* @param string $param
*
* @return string
*/
protected function parseCompare(Query $query, string $key, string $exp, $value, $field, int $bindType, ?string $param = null): 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);
} elseif ($value instanceof BackedEnum) {
$value = $value->value;
} elseif ($value instanceof UnitEnum) {
$value = $value->name;
}
if ('=' == $exp && is_null($value)) {
return $key . ' IS NULL';
}
if (is_null($param)) {
return $key . ' ' . $exp . ' ' . $value;
} else {
return '( ' . $key . ' ' . $exp . ' ' . $value . ' ) ' . $param;
}
}
/**
* 时间范围查询.
*
* @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
);
}
}
File diff suppressed because it is too large Load Diff
+839
View File
@@ -0,0 +1,839 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 BackedEnum;
use Closure;
use Stringable;
use think\db\BaseQuery as Query;
use think\db\exception\DbException as Exception;
use UnitEnum;
/**
* 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 UnitEnum) {
$val = $this->parseEnum($val);
} elseif ($val instanceof Raw) {
$result[$item] = $this->parseRaw($query, $val);
continue;
} elseif (is_null($val) && in_array($key, $fields, true)) {
$result[$item] = 'NULL';
continue;
} elseif (!is_scalar($val) && (in_array($key, (array) $query->getOption('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 ($val instanceof Express) {
if ($val->getLazyTime() && in_array($val->getType(), ['+', '-'])) {
$step = $query->lazyWrite($key, $val->getType() == '+' ? 'inc' : 'dec', $val->getStep(), $val->getLazyTime());
if (false === $step) {
continue;
}
$result[$item] = $item . ' + ' . $step;
} else {
$result[$item] = $item . $this->parseExpress($query, $val);
}
} 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)) {
return '*';
}
// 支持 'field1' => 'field2' 这样的字段别名定义
$array = array_map(function ($field, $key) use ($query) {
if ($field instanceof Raw) {
return $this->parseRaw($query, $field);
} elseif (!is_numeric($key)) {
// 字段别名定义
return $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true);
} else {
return $this->parseKey($query, $field);
}
}, $fields, array_keys($fields));
return implode(',', $array);
}
/**
* 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));
}
$param = $val[2] ?? null;
$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();
} elseif ($value instanceof UnitEnum) {
$value = $this->parseEnum($value);
}
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, $param);
}
}
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 = null): string
{
// 模糊匹配
$logic = $logic ?: 'AND';
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 . ' ';
}
/**
* 解析枚举类型值
*
* @param UnitEnum $value
*
* @return mixed
*/
protected function parseEnum(UnitEnum $value)
{
if ($value instanceof BackedEnum) {
$value = $value->value;
} else {
$value = $value->name;
}
return $value;
}
/**
* 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 {
// 检查枚举类型
if (is_subclass_of($value, UnitEnum::class, false)) {
if (is_subclass_of($value, BackedEnum::class, false)) {
$value = array_column($value::cases(), 'value');
} else {
$value = array_column($value::cases(), 'name');
}
} else {
$value = is_array($value) ? $value : array_unique(explode(',', (string) $value));
}
if (count($value) === 0) {
return 'IN' == $exp ? '0 = 1' : '1 = 1';
}
if ($query->isAutoBind()) {
$array = [];
foreach ($value as $v) {
if ($v instanceof UnitEnum) {
$v = $this->parseEnum($v);
}
$name = $query->bindValue($v, $bindType);
$array[] = ':' . $name;
}
$value = implode(',', $array);
} else{
foreach ($value as &$v) {
if ($v instanceof UnitEnum) {
$v = $this->parseEnum($v);
}
}
if (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|string)/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;
}
/**
* 分析Express对象
*
* @param Query $query 查询对象
* @param Express $express Express对象
*
* @return string
*/
protected function parseExpress(Query $query, Express $express): string
{
return $express->getValue();
}
/**
* 随机排序.
*
* @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
);
}
}
+220
View File
@@ -0,0 +1,220 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
}
}
+383
View File
@@ -0,0 +1,383 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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(): BaseQuery
{
$class = $this->getQueryClass();
return new $class($this);
}
/**
* 设置当前的数据库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->getOption('key')) && empty($method)) {
$key = 'think_' . $this->getConfig('database') . '.' . var_export($query->getTable(), true) . '|' . var_export($query->getOption('key'), true);
} 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) :
str_replace(
[':' . $key . ' ', ':' . $key . ',', ':' . $key . ')'],
[$value . ' ', $value . ',', $value . ')'],
$sql . ' ');
}
return rtrim($sql);
}
/**
* 析构方法.
*/
public function __destruct()
{
// 关闭连接
$this->close();
}
public function __call($method, $args)
{
// 调用Query类方法
return call_user_func_array([$this->newQuery(), $method], $args);
}
}
+208
View File
@@ -0,0 +1,208 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 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);
}
+64
View File
@@ -0,0 +1,64 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
/**
* SQL Express.
*/
class Express
{
/**
* 创建一个SQL运算表达式.
*
* @param string $type
* @param float $value
* @param int $lazyTime
*
* @return void
*/
public function __construct(protected string $type, protected float $step = 1, protected int $lazyTime = 0)
{
}
public function getStep()
{
return $this->step;
}
public function getType()
{
return $this->type;
}
public function getLazyTime()
{
return $this->lazyTime;
}
/**
* 获取表达式.
*
* @return string
*/
public function getValue(): string
{
return match ($this->type) {
'+' => ' + ' . $this->step,
'-' => ' - ' . $this->step,
'*' => ' * ' . $this->step,
'/' => ' / ' . $this->step,
default => ' + 0',
};
}
}
+544
View File
@@ -0,0 +1,544 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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->getOption('data', []), $data);
$this->query->setOption('data', $data);
if ($this->query->getOption('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 string $field 字段名
* @param float|int $step 步进值
* @param int $lazyTime 延迟时间(秒)
*
* @return string
*/
public function setInc(string $field, float|int $step = 1, int $lazyTime = 0)
{
return $this->inc($field, $step)->update();
}
/**
* 字段值减少
*
* @param string $field 字段名
* @param float|int $step 步进值
* @param int $lazyTime 延迟时间(秒)
*
* @return string
*/
public function setDec(string $field, float|int $step = 1, int $lazyTime = 0)
{
return $this->dec($field, $step)->update();
}
/**
* 更新记录.
*
* @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->getOption('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->update($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', '*');
}
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();
}
if (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;
}
}
+619
View File
@@ -0,0 +1,619 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 Countable;
use Generator;
use IteratorAggregate;
use JsonSerializable;
use think\Collection;
use think\contract\Arrayable;
use think\contract\Jsonable;
use think\Model;
use Traversable;
/**
* 惰性集合类 - 用于高效处理大数据集.
*
* @template TKey of array-key
* @template TModel of \think\Model
*
* @implements IteratorAggregate<TKey, TModel>
*/
class LazyCollection implements IteratorAggregate, Countable, JsonSerializable, Arrayable, Jsonable
{
/**
* 数据源生成器
* @var \Closure|Generator
*/
protected $source;
/**
* 缓存的总数
* @var int|null
*/
protected ?int $cachedCount = null;
/**
* 构造函数
* @param \Closure|Generator|BaseQuery $source 数据源
*/
public function __construct($source)
{
if ($source instanceof BaseQuery) {
$this->source = function () use ($source) {
yield from $source->cursor();
};
} elseif ($source instanceof \Closure) {
$this->source = $source;
} elseif ($source instanceof Generator) {
$this->source = function () use ($source) {
yield from $source;
};
} else {
throw new \InvalidArgumentException('Invalid source type for LazyCollection');
}
}
/**
* 创建惰性集合实例
* @param mixed $items 数据源
* @return static
*/
public static function make($items = [])
{
if ($items instanceof static) {
return $items;
}
if (is_array($items)) {
return new static(function () use ($items) {
foreach ($items as $key => $value) {
yield $key => $value;
}
});
}
return new static($items);
}
/**
* 获取生成器
* @return Traversable
*/
public function getIterator(): Traversable
{
return ($this->source)();
}
/**
* 映射集合项到新的惰性集合
* @param callable $callback 回调函数
* @return static
*/
public function map(callable $callback)
{
return new static(function () use ($callback) {
foreach ($this->getIterator() as $key => $value) {
yield $key => $callback($value, $key);
}
});
}
/**
* 过滤集合项
* @param callable|null $callback 回调函数
* @return static
*/
public function filter(?callable $callback = null)
{
if (is_null($callback)) {
$callback = function ($value) {
return (bool) $value;
};
}
return new static(function () use ($callback) {
foreach ($this->getIterator() as $key => $value) {
if ($callback($value, $key)) {
yield $key => $value;
}
}
});
}
/**
* 遍历集合项
* @param callable $callback 回调函数
* @return void
*/
public function each(callable $callback): void
{
foreach ($this->getIterator() as $key => $value) {
if ($callback($value, $key) === false) {
break;
}
}
}
/**
* 获取前N个元素
* @param int $limit 数量限制
* @return static
*/
public function take(int $limit)
{
if ($limit < 0) {
throw new \InvalidArgumentException('Limit should be non-negative');
}
return new static(function () use ($limit) {
$count = 0;
foreach ($this->getIterator() as $key => $value) {
if ($count >= $limit) {
break;
}
yield $key => $value;
$count++;
}
});
}
/**
* 跳过前N个元素
* @param int $offset 跳过数量
* @return static
*/
public function skip(int $offset)
{
if ($offset < 0) {
throw new \InvalidArgumentException('Offset should be non-negative');
}
return new static(function () use ($offset) {
$count = 0;
foreach ($this->getIterator() as $key => $value) {
if ($count >= $offset) {
yield $key => $value;
}
$count++;
}
});
}
/**
* 分页获取数据
* @param int $page 页码(从1开始)
* @param int $listRows 每页数量
* @return static
*/
public function page(int $page, int $listRows = 15)
{
if ($page < 1 || $listRows < 1) {
throw new \InvalidArgumentException(
$page < 1 ? 'Page should be at least 1' : 'Per page should be at least 1'
);
}
$offset = ($page - 1) * $listRows;
return new static(function () use ($offset, $listRows) {
$index = 0;
$taken = 0;
foreach ($this->getIterator() as $key => $value) {
if ($index++ < $offset) {
continue;
}
yield $key => $value;
if (++$taken >= $listRows) {
break;
}
}
});
}
/**
* 扁平化集合
* @param int|float $depth 深度
* @return static
*/
public function flatten($depth = INF)
{
return new static(function () use ($depth) {
foreach ($this->getIterator() as $item) {
if (is_array($item) || $item instanceof Traversable) {
if ($depth === 1) {
foreach ($item as $value) {
yield $value;
}
} else {
foreach (static::make($item)->flatten($depth - 1) as $value) {
yield $value;
}
}
} else {
yield $item;
}
}
});
}
/**
* 转换为数组
* @return array
*/
public function toArray(): array
{
$items = [];
foreach ($this->getIterator() as $key => $value) {
if ($value instanceof Arrayable) {
$items[$key] = $value->toArray();
} else {
$items[$key] = $value;
}
}
return $items;
}
/**
* 转换为JSON
* @param int $options JSON选项
* @return string
*/
public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
{
return json_encode($this->jsonSerialize(), $options);
}
/**
* JsonSerializable接口实现
* @return array
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
/**
* 获取集合数量
* @return int
*/
public function count(): int
{
if ($this->cachedCount !== null) {
return $this->cachedCount;
}
$count = 0;
foreach ($this->getIterator() as $value) {
$count++;
}
$this->cachedCount = $count;
return $count;
}
/**
* 判断是否为空
* @return bool
*/
public function isEmpty(): bool
{
foreach ($this->getIterator() as $value) {
return false;
}
return true;
}
/**
* 获取第一个元素
* @param callable|null $callback 回调函数
* @param mixed $default 默认值
* @return mixed
*/
public function first(?callable $callback = null, $default = null)
{
foreach ($this->getIterator() as $key => $value) {
if (is_null($callback) || $callback($value, $key)) {
return $value;
}
}
return $default;
}
/**
* 获取最后一个元素
* @param callable|null $callback 回调函数
* @param mixed $default 默认值
* @return mixed
*/
public function last(?callable $callback = null, $default = null)
{
$last = $default;
$lastKey = null;
foreach ($this->getIterator() as $key => $value) {
if (is_null($callback) || $callback($value, $key)) {
$last = $value;
$lastKey = $key;
}
}
return $last;
}
/**
* 按键值对集合进行分组
* @param callable|string|int $groupBy 分组依据
* @return static
*/
public function group($groupBy)
{
return new static(function () use ($groupBy) {
$groups = [];
foreach ($this->getIterator() as $key => $value) {
if (is_callable($groupBy)) {
$groupKey = $groupBy($value, $key);
} else {
$groupKey = data_get($value, $groupBy);
}
if (!isset($groups[$groupKey])) {
$groups[$groupKey] = [];
}
$groups[$groupKey][$key] = $value;
}
foreach ($groups as $key => $group) {
yield $key => new Collection($group);
}
});
}
/**
* 对集合进行排序
* @param callable|null $callback 排序回调
* @return Collection
*/
public function sort(?callable $callback = null): Collection
{
$items = $this->toArray();
if (is_null($callback)) {
asort($items);
} else {
uasort($items, $callback);
}
return new Collection($items);
}
/**
* 指定字段排序
*
* @param string $field 排序字段
* @param string $order 排序
* @return $this
*/
public function order(string $field, string $order = 'asc')
{
return $this->sort(function ($a, $b) use ($field, $order) {
$fieldA = $a[$field] ?? null;
$fieldB = $b[$field] ?? null;
return 'desc' == strtolower($order) ? ($fieldB <=> $fieldA) : ($fieldA <=> $fieldB);
});
}
/**
* 对集合进行反转
* @return static
*/
public function reverse()
{
return new static(function () {
$items = [];
foreach ($this->getIterator() as $key => $value) {
$items[] = [$key, $value];
}
for ($i = count($items) - 1; $i >= 0; $i--) {
yield $items[$i][0] => $items[$i][1];
}
});
}
/**
* 使用回调函数迭代集合项并将结果传递给下一次迭代
* @param callable $callback 回调函数
* @param mixed $initial 初始值
* @return mixed
*/
public function reduce(callable $callback, $initial = null)
{
$result = $initial;
foreach ($this->getIterator() as $key => $value) {
$result = $callback($result, $value, $key);
}
return $result;
}
/**
* 执行给定的回调,除非给定的条件为真
* @param bool $condition 条件
* @param callable $callback 回调函数
* @param callable|null $default 默认回调
* @return static
*/
public function when(bool $condition, callable $callback, ?callable $default = null)
{
if ($condition) {
return $callback($this) ?: $this;
} elseif ($default) {
return $default($this) ?: $this;
}
return $this;
}
/**
* 根据字段条件过滤数组中的元素
*
* @param string $field 字段名
* @param mixed $operator 操作符
* @param mixed $value 数据
* @return static<TKey, TValue>
*/
public function where(string $field, $operator, $value = null)
{
if (is_null($value)) {
$value = $operator;
$operator = '=';
}
return $this->filter(function ($item) use ($field, $operator, $value) {
$result = data_get($item, $field);
return match ($operator) {
'!=' => $result != $value,
'<>' => $result != $value,
'<' => $result < $value,
'<=' => $result <= $value,
'>' => $result > $value,
'>=' => $result >= $value,
'===' => $result === $value,
'!==' => $result !== $value,
'in' => is_scalar($result) && is_array($value) && in_array($result, $value),
'not in' => is_scalar($result) && is_array($value) && !in_array($result, $value),
'between' => is_array($value) && count($value) === 2 && ($result >= $value[0] && $result <= $value[1]),
'not between' => is_array($value) && count($value) === 2 && ($result < $value[0] || $result > $value[1]),
'like' => is_string($result) && str_contains($result, $value),
'not like' => is_string($result) && !str_contains($result, $value),
'ilike' => is_string($result) && str_contains(strtolower($result), strtolower($value)),
'start'=> is_string($result) && str_starts_with($result, $value),
'end' => is_string($result) && str_ends_with($result, $value),
default => $result == $value,
};
});
}
/**
* LIKE过滤
*
* @param string $field 字段名
* @param string $value 数据
* @param bool $case 区分大小写
* @return static<TKey, TValue>
*/
public function whereLike(string $field, string $value, bool $case = true)
{
return $this->where($field, $case ? 'like' : 'ilike', $value);
}
/**
* NOT LIKE过滤
*
* @param string $field 字段名
* @param string $value 数据
* @return static<TKey, TValue>
*/
public function whereNotLike(string $field, string $value)
{
return $this->where($field, 'not like', $value);
}
/**
* IN过滤
*
* @param string $field 字段名
* @param array $value 数据
* @return static<TKey, TValue>
*/
public function whereIn(string $field, array $value)
{
return $this->where($field, 'in', $value);
}
/**
* NOT IN过滤
*
* @param string $field 字段名
* @param array $value 数据
* @return static<TKey, TValue>
*/
public function whereNotIn(string $field, array $value)
{
return $this->where($field, 'not in', $value);
}
/**
* BETWEEN 过滤
*
* @param string $field 字段名
* @param mixed $value 数据
* @return static<TKey, TValue>
*/
public function whereBetween(string $field, $value)
{
return $this->where($field, 'between', $value);
}
/**
* NOT BETWEEN 过滤
*
* @param string $field 字段名
* @param mixed $value 数据
* @return static<TKey, TValue>
*/
public function whereNotBetween(string $field, $value)
{
return $this->where($field, 'not between', $value);
}
/**
* 分块处理数据
* @param int $size 块大小
* @return static
*/
public function chunk(int $size)
{
if ($size <= 0) {
throw new \InvalidArgumentException('Size should be greater than 0');
}
return new static(function () use ($size) {
$chunk = [];
$count = 0;
foreach ($this->getIterator() as $key => $value) {
$chunk[$key] = $value;
$count++;
if ($count >= $size) {
yield new Collection($chunk);
$chunk = [];
$count = 0;
}
}
if (!empty($chunk)) {
yield new Collection($chunk);
}
});
}
}
+802
View File
@@ -0,0 +1,802 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 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|int $step 增长值
*
* @return $this
*/
public function inc(string $field, float|int $step = 1)
{
$this->options['data'][$field] = ['$inc', $step];
return $this;
}
/**
* 字段值减少.
*
* @param string $field 字段名
* @param float|int $step 减少值
*
* @return $this
*/
public function dec(string $field, float|int $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'] = array_map(function ($val) {
return 'asc' == strtolower($val) ? 1 : -1;
}, $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 cursor(): Cursor
{
return $this->getCursor();
}
/**
* 执行查询但只返回Cursor对象
*
* @return Cursor
*/
public function getCursor(): Cursor
{
$this->parseOptions();
return $this->connection->cursor($this);
}
/**
* 分页查询.
*
* @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;
if ($key == '_id' && $this->connection->getConfig('pk_convert_id')) {
$key = 'id';
}
}
}
$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;
}
/**
* 获取字段类型
*
* @return array
*/
public function getType()
{
return $this->getFieldsType();
}
/**
* 获取自增主键
*
* @return string
*/
public function getAutoInc()
{
return '';
}
/**
* 设置自增主键
*
* @param string $autoInc
* @return static
*/
public function autoInc(?string $autoInc)
{
return $this;
}
}
File diff suppressed because it is too large Load Diff
+645
View File
@@ -0,0 +1,645 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\Collection;
use think\db\exception\DbException as Exception;
use think\model\LazyCollection as ModelLazyCollection;
/**
* PDO数据查询类.
*/
class Query extends BaseQuery
{
use concern\JoinAndViewQuery;
use concern\TableFieldInfo;
use concern\Transaction;
/**
* 表达式方式指定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($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|null
*/
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|int $step 增长值
* @param int $lazyTime 延迟时间(秒)
*
* @return $this
*/
public function inc(string $field, float|int $step = 1, int $lazyTime = 0)
{
if ($lazyTime > 0) {
$step = $this->lazyWrite($field, 'inc', $step, $lazyTime);
if (false === $step) {
return $this;
}
}
$this->options['data'][$field] = new Express('+', $step);
return $this;
}
/**
* 字段值减少.
*
* @param string $field 字段名
* @param float|int $step 增长值
* @param int $lazyTime 延迟时间(秒)
*
* @return $this
*/
public function dec(string $field, float|int $step = 1, int $lazyTime = 0)
{
if ($lazyTime > 0) {
$step = $this->lazyWrite($field, 'dec', $step, $lazyTime);
if (false === $step) {
return $this;
}
return $this->inc($field, $step);
}
$this->options['data'][$field] = new Express('-', $step);
return $this;
}
/**
* 字段值增长(支持延迟写入)
*
* @param string $field 字段名
* @param float|int $step 步进值
* @param int $lazyTime 延迟时间(秒)
*
* @return int|false
*/
public function setInc(string $field, float|int $step = 1, int $lazyTime = 0)
{
return $this->inc($field, $step, $lazyTime)->update();
}
/**
* 字段值减少(支持延迟写入)
*
* @param string $field 字段名
* @param float|int $step 步进值
* @param int $lazyTime 延迟时间(秒)
*
* @return int|false
*/
public function setDec(string $field, float|int $step = 1, int $lazyTime = 0)
{
return $this->dec($field, $step, $lazyTime)->update();
}
/**
* 延时更新检查 返回false表示需要延时
* 否则返回实际写入的数值
* @access public
* @param string $field 字段名
* @param string $type 自增或者自减
* @param float|int $step 写入步进值
* @param int $lazyTime 延时时间(s)
* @return false|integer
*/
public function lazyWrite(string $field, string $type, float|int $step, int $lazyTime)
{
$guid = $this->getLazyFieldCacheKey($field);
$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());
}
/**
* 执行查询但只返回PDOStatement对象
*
* @return PDOStatement
*/
public function getPdo(): PDOStatement
{
return $this->connection->pdo($this);
}
/**
* 使用游标查找记录.(不支持查询缓存)
*
* @param bool $unbuffered 是否开启无缓冲查询(仅限mysql)
*
* @return LazyCollection
*/
public function cursor(bool $unbuffered = false): LazyCollection
{
$connection = clone $this->connection;
$class = $this->model ? ModelLazyCollection::class : LazyCollection::class;
if (!empty($this->options['with_join'])) {
$withJoin = true;
$relations = $this->options['with_join'];
} elseif(!empty($this->options['with'])) {
$withJoin = false;
$relations = $this->options['with'];
}
if (isset($withJoin)) {
return (new $class(function () use ($connection, $unbuffered) {
yield from $connection->cursor($this, $unbuffered);
}))->load($relations, false, $withJoin);
} else {
return new $class(function () use ($connection, $unbuffered) {
yield from $connection->cursor($this, $unbuffered);
});
}
}
/**
* 流式处理查询结果(不支持查询缓存)
*
* @param callable $callback 处理回调
* @param bool $unbuffered 是否使用无缓冲查询(仅MySQL支持)
*
* @return int 处理的记录数
*/
public function stream(callable $callback, bool $unbuffered = false): int
{
$count = 0;
$this->cursor($unbuffered)
->each(function ($item) use ($callback, &$count) {
$callback($item);
$count++;
});
return $count;
}
/**
* 分批数据返回处理.
*
* @param int $size 每次处理的数据数量
* @param callable $callback 处理回调方法
* @param string|null $column 分批处理的字段名
* @param string $order 字段排序
*
* @throws Exception
*
* @return bool
*/
public function chunk(int $size, callable $callback, string | null $column = null, string $order = 'asc'): bool
{
$chunks = $this->lazy($size, $column, $order)->chunk($size);
foreach ($chunks as $chunk) {
$result = $callback($chunk);
if ($result === false) {
return false;
}
}
return true;
}
/**
* 惰性分批遍历数据
* @param int $count 每批处理的数量
* @param string|null $column 分批处理的字段名
* @param string $order 字段排序
* @return LazyCollection
*/
public function lazy(int $size = 1000, ?string $column = null, string $order = 'desc'): LazyCollection
{
if ($size < 1) {
throw new Exception('The chunk size should be at least 1');
}
$class = $this->model ? ModelLazyCollection::class : LazyCollection::class;
return new $class(function () use ($size, $column, $order) {
$limit = (int) $this->getOption('limit', 0);
$column = $column ?: $this->getPk();
$length = $limit && $size >= $limit ? $limit : $size;
$options = $this->getOptions();
$bind = $this->bind;
$times = 0;
$field = $this->getOption('field');
if (is_array($field) && false !== stripos(implode(',', $field), 'distinct')) {
$distinct = true;
} else {
$distinct = false;
}
if ($this->getOption('order') || !is_string($column) || $distinct) {
$page = 1;
$resultSet = $this->options($options)->page($page, $length)->select();
} else {
$resultSet = $this->options($options)->order($column, $order)->limit($length)->select();
}
while (true) {
foreach ($resultSet as $item) {
yield $item;
$times++;
if ($limit > $size && $times >= $limit) {
break 2;
}
if (!isset($page)) {
$lastId = $item[$column];
}
}
if (count($resultSet) < $size) {
break;
}
if (isset($page)) {
$page++;
$query = $this->options($options)->page($page, $length);
} else {
$query = $this->options($options)
->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId)
->order($column, $order)
->limit($length);
}
$resultSet = $query->bind($bind)->select();
}
});
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 array
*/
public function getBind(): array
{
return $this->bind;
}
public function __toString()
{
return $this->value;
}
}
+175
View File
@@ -0,0 +1,175 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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): mixed
{
return $this->__get($name);
}
}
+692
View File
@@ -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 Closure;
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->getOption('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);
}
}
+525
View File
@@ -0,0 +1,525 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\BaseQuery as Query;
use think\db\Builder;
use think\db\exception\DbException as Exception;
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;
}
if ($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) . '\'';
}
if (str_contains($key, '->') && !str_contains($key, '(')) {
// JSON字段支持
[$field, $name] = explode('->', $key, 2);
return 'json_unquote(json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (str_starts_with($name, '[') ? '' : '.') . str_replace('->', '.', $name) . '\'))';
}
if (str_contains($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
[$table, $key] = explode('.', $key, 2);
$alias = $query->getOption('alias');
if ('__TABLE__' == $table) {
$table = $query->getOption('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_unquote(json_extract")) {
if ('NULL' === $exp) {
return '(' . $key . ' is null OR ' . $key . ' = \'null\')';
} elseif ('NOT NULL' === $exp) {
return '(' . $key . ' is not null AND ' . $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) . ' ';
}
}
+134
View File
@@ -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->getOption('alias');
if ('__TABLE__' == $table) {
$table = $query->getOption('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';
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 . '\'';
} else {
if (str_contains($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
[$table, $key] = explode('.', $key, 2);
$alias = $query->getOption('alias');
if ('__TABLE__' == $table) {
$table = $query->getOption('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)) {
if (str_contains($table, '.')) {
$table = str_replace('.', '"."', $table);
}
$key = '"' . $table . '".' . $key;
}
return $key;
}
/**
* 随机排序.
*
* @param Query $query 查询对象
*
* @return string
*/
protected function parseRand(Query $query): string
{
return 'RANDOM()';
}
}
+118
View File
@@ -0,0 +1,118 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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->getOption('alias');
if ('__TABLE__' == $table) {
$table = $query->getOption('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 '';
}
}
+194
View File
@@ -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->getOption('alias');
if ('__TABLE__' == $table) {
$table = $query->getOption('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,131 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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)
{
return $this->connection->aggregate($this, $aggregate, $field, $force);
}
/**
* COUNT查询.
*
* @param string $field 字段名
*
* @return int
*/
public function count(string $field = '*'): int
{
if (!empty($this->options['group'])) {
return $this->countWithGroup($field);
}
return (int) $this->aggregate('COUNT', $field);
}
protected function countWithGroup(string $field): int
{
if (!preg_match('/^[\w\.\*]+$/', $field)) {
throw new DbException('Not supported data: ' . $field);
}
$options = $this->getOptions();
$cache = $options['cache'] ?? null;
if (isset($options['cache'])) {
unset($options['cache']);
}
$subSql = $this->options($options)
->field('count(' . $field . ') AS think_count')
->bind($this->bind)
->buildSql();
$query = $this->newQuery();
if ($cache) {
$query->setOption('cache', $cache);
}
$query->table([$subSql => '_group_count_']);
return (int) $query->aggregate('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,241 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
use think\helper\Str;
/**
* 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, $alias);
if (!empty($bind) && $condition) {
$this->bindParams($condition, $bind);
}
if ($alias) {
$this->options['join'][$alias] = [$table, strtoupper($type), $condition];
} else {
$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', $bind);
}
/**
* 获取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;
}
if ($join instanceof Raw || str_contains($join, '(')) {
return $join;
}
$join = trim($join);
// 使用别名
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->prefix . Str::snake($table) . $this->suffix;
}
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 = []): self
{
$this->options['view'] = true;
$fields = [];
$table = $this->getJoinTable($join, $alias);
// 处理字段
$fields = $this->processFields($field, $alias);
$this->field($fields);
// 处理连接
if ($on) {
$this->join($table, $on, $type, $bind);
} else {
$this->table($table);
}
return $this;
}
protected function processFields(string | array | bool $field, string $alias): array
{
$fields = [];
if (true === $field) {
$fields[] = $alias . '.*'; // 选取所有字段
} else {
if (is_string($field)) {
$field = explode(',', $field);
}
foreach ($field as $key => $val) {
$name = is_numeric($key) ? $alias . '.' . $val : (preg_match('/[,=\.\'\"\(\s]/', $key) ? $key : $alias . '.' . $key);
$fields[] = $name . (is_numeric($key) ? '' : ' AS ' . $val);
$this->options['map'][$val] = $name;
}
}
return $fields;
}
/**
* 视图查询处理.
*
* @param array $options 查询参数
*
* @return void
*/
protected function parseView(array &$options): void
{
foreach (['AND', 'OR'] as $logic) {
if (!isset($options['where'][$logic])) {
continue;
}
// 视图查询条件处理
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 (empty($options['order'])) {
return;
}
// 视图查询排序处理
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,729 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\Collection as ModelCollection;
use think\model\contract\Modelable as Model;
/**
* 模型及关联查询.
*/
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 属性列表
* @param bool $merge 是否合并
*
* @return $this
*/
public function hidden(array $hidden, bool $merge = false)
{
$this->options['hidden'] = [$hidden, $merge];
return $this;
}
/**
* 设置需要输出的属性.
*
* @param array $visible 属性列表
* @param bool $merge 是否合并
*
* @return $this
*/
public function visible(array $visible, bool $merge = false)
{
$this->options['visible'] = [$visible, $merge];
return $this;
}
/**
* 设置需要附加的输出属性.
*
* @param array $append 属性列表
* @param bool $merge 是否合并
*
* @return $this
*/
public function append(array $append, bool $merge = false)
{
$this->options['append'] = [$append, $merge];
return $this;
}
/**
* 设置模型的输出映射.
*
* @param array $mapping 映射列表
*
* @return $this
*/
public function mapping(array $mapping)
{
$this->options['mapping'] = $mapping;
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) {
$this->options['scope'][] = [$scope, $args];
return $this;
}
if ($this->model) {
if (is_string($scope)) {
$scope = explode(',', $scope);
}
// 检查模型类的查询范围方法
$entity = $this->model->getEntity();
foreach ($scope as $name) {
$method = 'scope' . trim($name);
if ($entity && method_exists($entity, $method)) {
$this->options['scope'][$name] = [[$entity, $method], $args];
} elseif (method_exists($this->model, $method)) {
$this->options['scope'][$name] = [[$this->model, $method], $args];
}
}
}
return $this;
}
/**
* 执行查询范围查询.
*
* @return $this
*/
protected function scopeQuery()
{
if (!empty($this->options['scope'])) {
foreach ($this->options['scope'] as $val) {
[$call, $args] = $val;
call_user_func_array($call, $args);
}
}
return $this;
}
/**
* 指定不使用的查询范围.
*
* @param array $scope 查询范围
*
* @return $this
*/
public function withoutScope(array $scope = [])
{
if (empty($scope)) {
$this->options['scope'] = [];
return $this;
}
foreach ($scope as $name) {
if (isset($this->options['scope'][$name])) {
unset($this->options['scope'][$name]);
}
}
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 bool $strict 是否严格检查数据
*
* @return $this
*/
public function withSearch(string | array $fields, $data = [], bool $strict = false)
{
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);
} elseif ($this->model) {
// 检查字段是否有数据
$fieldName = is_numeric($key) ? $field : $key;
if ($strict && (!isset($data[$fieldName]) || (empty($data[$fieldName]) && !in_array($data[$fieldName], ['0', 0])))) {
continue;
}
if (is_string($key) && isset($data[$key])) {
// 默认搜索规则
$this->where($key, $field, 'like' == $field ? '%' . $data[$key] . '%' : $data[$key]);
continue;
}
$method = 'search' . Str::studly($fieldName) . 'Attr';
$entity = $this->model->getEntity();
if ($entity && method_exists($entity, $method)) {
$entity->$method($this, $data[$fieldName] ?? null, $data);
} elseif (method_exists($this->model, $method)) {
$this->model->$method($this, $data[$fieldName] ?? null, $data);
} 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 array $attr 绑定数据
*
* @return $this
*/
public function withBind(array $attr)
{
$this->options['bind_attr'] = $attr;
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_merge($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 = 'id', 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 string $field 字段(默认为id)
* @param bool $subQuery 是否使用子查询
*
* @return $this
*/
public function withCount(string | array $relation, string $field = 'id', bool $subQuery = true)
{
return $this->withAggregate($relation, 'count', $field, $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 string $id 关联表的统计字段
* @param string $joinType JOIN类型
*
* @return $this
*/
public function hasNot(string $relation, string $id = '*', string $joinType = '')
{
return $this->model->has($relation, '=', 0, $id, $joinType, $this);
}
/**
* 根据关联条件查询当前模型.
*
* @param string|array $relation 关联方法名 或 ['关联方法名', '关联表别名']
* @param mixed $where 查询条件(数组或者闭包)
* @param mixed $fields 字段
* @param string $joinType JOIN类型
*
* @return $this
*/
public function hasWhere(string|array $relation, $where = [], string $fields = '*', string $joinType = '')
{
return $this->model->hasWhere($relation, $where, $fields, $joinType, $this);
}
/**
* 根据关联条件查询当前模型.
*
* @param string|array $relation 关联方法名 或 ['关联方法名', '关联表别名']
* @param mixed $where 查询条件(数组或者闭包)
* @param mixed $fields 字段
* @param string $joinType JOIN类型
*
* @return $this
*/
public function hasWhereOr(string|array $relation, $where = [], string $fields = '*', string $joinType = '')
{
return $this->model->hasWhereOr($relation, $where, $fields, $joinType, $this);
}
/**
* 查询数据转换为模型数据集对象
*
* @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
{
// 实时读取延迟数据
if (!empty($this->options['lazy_fields'])) {
$id = $this->getKey($result);
foreach ($this->options['lazy_fields'] as $field) {
if (isset($result[$field])) {
$result[$field] += $this->getLazyFieldValue($field, $id);
}
}
}
$result = $this->model->newInstance($result, $this->options);
if ($this->suffix) {
$result->setSuffix($this->suffix);
}
// 模型数据处理
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(
$result,
$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->withFieldAttr($this->options['with_attr']);
}
// 模型输出设置
foreach (['hidden', 'visible', 'append'] as $name) {
if (!empty($this->options[$name])) {
[$value, $merge] = $this->options[$name];
$result->$name($value, $merge);
}
}
// 字段映射
if (!empty($this->options['mapping'])) {
$result->mapping($this->options['mapping']);
}
}
}
+149
View File
@@ -0,0 +1,149 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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->getOption('auto_bind')) {
$autoBind = $this->getOption('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);
} elseif (str_ends_with($sql, ':' . $key)) {
$sql = substr_replace($sql, ':' . $name . ' ', strrpos($sql, ':' . $key), strlen(':' . $key));
} else {
$sql = str_replace(
[':' . $key . ' ', ':' . $key . ',', ':' . $key . ')'],
[':' . $name . ' ', ':' . $name . ',', ':' . $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,266 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\contract\Modelable as 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']);
}
// 检查字段映射
if (!empty($this->options['mapping'])) {
foreach ($this->options['mapping'] as $name => $alias) {
if (isset($result[$name])) {
$result[$alias] = $result[$name];
unset($result[$name]);
}
}
}
}
/**
* 处理数据集.
*
* @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);
}
}
}
/**
* 处理空数据.
* @param Closure $closure 闭包数据
* @throws DbException
* @throws ModelNotFoundException
* @throws DataNotFoundException
*
* @return array|Model|null|static
*/
protected function resultToEmpty(?Closure $closure = null)
{
if (!empty($this->options['fail'])) {
$this->throwNotFound();
} elseif ($closure instanceof Closure) {
return $closure($this);
} 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,107 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 && !empty($this->options['field_type'])) {
return array_keys($this->options['field_type']);
}
return $this->connection->getTableFields($tableName ?: $this->getTable());
}
/**
* 获取详细字段类型信息.
*
* @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());
}
public function getType(): array
{
return $this->getFieldsType();
}
/**
* 获取字段类型信息.
*
* @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,227 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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'])) {
if($month === 'last month') {
$month = $this->timeRule['last month'][0];
}
$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');
}
}
+131
View File
@@ -0,0 +1,131 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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);
}
}
+670
View File
@@ -0,0 +1,670 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 self) {
$this->parseQueryWhere($field);
return $this;
} elseif (true === $field || 1 === $field) {
$this->options['where']['AND'][] = true;
return $this;
} elseif (empty($field)) {
return $this;
}
$pk = $this->getPk();
if ((is_null($condition) || '=' == $op) && is_string($pk) && $pk == $field) {
$this->options['key'] = is_null($condition) ? $op : $condition;
}
$logic = 'AND';
$param = func_get_args();
array_shift($param);
if (is_array($field) && !empty($field) && array_is_list($field)) {
return $this->where(function ($query) use ($param, $condition, $op, $field, $logic) {
return $query->parseWhereExp($logic, $field, $op, $condition, $param);
});
}
return $this->parseWhereExp($logic, $field, $op, $condition, $param);
}
/**
* 解析Query对象查询条件.
*
* @param BaseQuery $query 查询对象
*
* @return void
*/
protected function parseQueryWhere(BaseQuery $query): void
{
$this->options['where'] = $query->getOption('where', []);
$via = $query->getOption('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)
{
$logic = 'OR';
$param = func_get_args();
array_shift($param);
if (is_array($field) && !empty($field) && array_is_list($field)) {
return $this->where(function ($query) use ($param, $condition, $op, $field, $logic) {
return $query->parseWhereExp($logic, $field, $op, $condition, $param);
});
}
return $this->parseWhereExp($logic, $field, $op, $condition, $param);
}
/**
* 指定XOR查询条件.
*
* @param mixed $field 查询字段
* @param mixed $op 查询表达式
* @param mixed $condition 查询条件
*
* @return $this
*/
public function whereXor($field, $op = null, $condition = null)
{
$logic = 'XOR';
$param = func_get_args();
array_shift($param);
if (is_array($field) && !empty($field) && array_is_list($field)) {
return $this->where(function ($query) use ($param, $condition, $op, $field, $logic) {
return $query->parseWhereExp($logic, $field, $op, $condition, $param);
});
}
return $this->parseWhereExp($logic, $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);
}
/**
* 指定json_contains查询条件.
*
* @param mixed $field 查询字段
* @param mixed $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereJsonContains(string $field, $condition, string $logic = 'AND')
{
$value = is_null($condition) ? 'NULL' : '\'' . json_encode($condition) . '\'';
if (str_contains($field, '->')) {
[$field, $path] = explode('->', $field, 2);
return $this->whereRaw('json_contains(' . $field . ', ' . $value . ', \'$.'. str_replace('->', '.', $path) . '\')', [], $logic);
}
return $this->whereRaw('json_contains(' . $field . ', ' . $value . ')', [], $logic);
}
public function whereOrJsonContains(string $field, $condition)
{
return $this->whereJsonContains($field, $condition, 'OR');
}
/**
* 比较两个字段.
*
* @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;
}
/**
* 包含软删除数据.
*
* @return $this
*/
public function withTrashed()
{
if ($this->model) {
$this->options['soft_delete'] = null;
}
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): self
{
$logic = strtoupper($logic);
// 字段映射
$map = $this->getOption('field_map', []);
if (is_string($field) && isset($map[$field])) {
$field = $map[$field];
}
// 处理 via
if (is_string($field) && !empty($this->options['via']) && !str_contains($field, '.') && !str_contains($field, '->')) {
$field = $this->options['via'] . '.' . $field;
}
// 严格模式查询
if ($strict) {
return $this->parseStrictWhere($field, $op, $condition, $logic);
}
// 处理批量查询
if (is_array($field)) {
return $this->parseArrayWhereItems($field, $logic);
}
// 处理闭包查询
if ($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;
}
protected function parseStrictWhere($field, $op, $condition, string $logic): self
{
if ('=' === $op) {
$where = $this->whereEq($field, $condition);
} else {
$where = [$field, $op, $condition, $logic];
}
$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);
return $param;
}
if (is_string($field) && strpos($field, '->')) {
[$relation, $attr] = explode('->', $field, 2);
$type = $this->getFieldType($relation);
if (is_null($type)) {
// 自动关联查询
$this->hasWhere($relation, [[$attr , is_null($condition) ? '=' : $op, $condition ?? $op]]);
return [];
}
}
if ($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);
}
return $where;
}
if (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, Closure | array $query, Closure | array | null $otherwise = null)
{
// 处理条件为 Closure 的情况
if ($condition instanceof Closure) {
$condition = $condition($this);
}
// 根据条件决定执行哪个查询
if ($condition) {
$this->executeQuery($query, $condition);
} elseif ($otherwise) {
$this->executeQuery($otherwise, $condition);
}
return $this;
}
protected function executeQuery(Closure | array $query, $condition): void
{
if ($query instanceof Closure) {
$query($this, $condition);
} elseif (is_array($query)) {
$this->where($query);
}
}
}
File diff suppressed because it is too large Load Diff
+227
View File
@@ -0,0 +1,227 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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'");
}
/**
* 使用无缓冲游标查询记录(MySQL专用)
*
* @param \think\db\BaseQuery $query 查询对象
* @param string $sql 查询SQL
*
* @return \Generator
*/
protected function cursorUnbuffered(\think\db\BaseQuery $query, string $sql)
{
// 启用无缓冲查询模式
$this->initUnbufferedMode();
try {
// 执行查询操作
yield from $this->getCursor($query, $sql, $query->getModel());
} finally {
// 恢复缓冲模式
$this->restoreBufferedMode();
}
}
/**
* 开启无缓冲查询模式
*
* @return void
*/
protected function initUnbufferedMode(): void
{
if ($this->linkID) {
$this->linkID->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
}
}
/**
* 恢复缓冲查询模式
*
* @return void
*/
protected function restoreBufferedMode(): void
{
if ($this->linkID) {
$this->linkID->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
}
}
/**
* 获取设置时区的SQL语句.
*
* @param string $timezone 时区名称,如 'Asia/Shanghai' 或 '+08:00'
*
* @return string
*/
protected function getSetTimezoneSql(string $timezone): string
{
return "SET time_zone = '$timezone'";
}
}
+137
View File
@@ -0,0 +1,137 @@
<?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\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;
}
/**
* 获取设置时区的SQL语句.
*
* @param string $timezone 时区名称,如 'Asia/Shanghai' 或 '+08:00'
*
* @return string
*/
protected function getSetTimezoneSql(string $timezone): string
{
return "ALTER SESSION SET TIME_ZONE = '$timezone'";
}
}
+163
View File
@@ -0,0 +1,163 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
/**
* 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);
// 分离 schema 和表名
if (str_contains($tableName, '.')) {
[$schema, $tableName] = explode('.', $tableName);
} else {
$schema = 'public';
}
// 使用标准 PostgreSQL 系统表查询字段信息
$sql = <<<SQL
SELECT
a.attname AS "field",
CASE t.typname
WHEN 'int8' THEN 'bigint'
WHEN 'int4' THEN 'integer'
WHEN 'int2' THEN 'smallint'
WHEN 'bpchar' THEN 'char'
WHEN 'float4' THEN 'float'
WHEN 'float8' THEN 'double'
ELSE t.typname
END AS "type",
a.attnotnull AS "notnull",
(p.contype = 'p') AS "primary",
pg_get_expr(d.adbin, d.adrelid) AS "default",
(
pg_get_serial_sequence(format('%I.%I', n.nspname, c.relname), a.attname) IS NOT NULL
OR a.attidentity <> ''
) AS "autoinc",
col_description(c.oid, a.attnum) AS "comment"
FROM
pg_attribute a
JOIN pg_class c ON a.attrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
JOIN pg_type t ON a.atttypid = t.oid
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
LEFT JOIN pg_constraint p ON a.attrelid = p.conrelid AND a.attnum = ANY(p.conkey) AND p.contype = 'p'
WHERE
c.relname = '{$tableName}'
AND n.nspname = '{$schema}'
AND a.attnum > 0
AND NOT a.attisdropped
ORDER BY
a.attnum
SQL;
$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['notnull'],
'default' => $val['default'],
'primary' => (bool) $val['primary'],
'autoinc' => (bool) $val['autoinc'],
'comment' => $val['comment'],
];
}
}
return $this->fieldCase($info);
}
/**
* 取得数据库的表信息.
*
* @param string $dbName
*
* @return array
*/
public function getTables(string $dbName = ''): array
{
$sql = "SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename";
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
return array_column($result, 'tablename');
}
protected function supportSavepoint(): bool
{
return true;
}
/**
* 获取设置时区的SQL语句.
*
* @param string $timezone 时区名称,如 'Asia/Shanghai' 或 '+08:00'
*
* @return string
*/
protected function getSetTimezoneSql(string $timezone): string
{
return "SET timezone = '$timezone'";
}
}
+111
View File
@@ -0,0 +1,111 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
}
/**
* 获取设置时区的SQL语句.
*
* @param string $timezone 时区名称,如 'Asia/Shanghai' 或 '+08:00'
*
* @return string SQLite 不支持时区设置,返回空字符串
*/
protected function getSetTimezoneSql(string $timezone): string
{
// SQLite 是嵌入式数据库,不支持时区设置
// 建议在应用层处理时区转换
return '';
}
}
+145
View File
@@ -0,0 +1,145 @@
<?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>
// +----------------------------------------------------------------------
declare(strict_types=1);
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;
}
/**
* 获取设置时区的SQL语句.
*
* @param string $timezone 时区名称,如 'Asia/Shanghai' 或 '+08:00'
*
* @return string
*/
protected function getSetTimezoneSql(string $timezone): string
{
// SQL Server 使用 SET TIME ZONE 命令
// 注意:SQL Server 2016 及以上版本才支持此命令
return "SET TIME ZONE '$timezone'";
}
}
@@ -0,0 +1,35 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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~2025 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,21 @@
<?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>
// +----------------------------------------------------------------------
declare(strict_types = 1);
namespace think\db\exception;
/**
* Db事件异常.
*/
class DbEventException extends DbException
{
}
@@ -0,0 +1,45 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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);
}
}
@@ -0,0 +1,21 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
/**
* Duplicate异常处理类
*/
class DuplicateException extends PDOException
{
}
@@ -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,21 @@
<?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>
// +----------------------------------------------------------------------
declare(strict_types = 1);
namespace think\db\exception;
/**
* 模型事件异常.
*/
class ModelEventException extends DbException
{
}
@@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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~2025 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);
}
}