This commit is contained in:
Your Name
2026-03-01 14:17:59 +08:00
parent 65aa12f146
commit a07e844c47
6071 changed files with 777651 additions and 0 deletions
+960
View File
@@ -0,0 +1,960 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db;
use Closure;
use think\db\BaseQuery as Query;
use think\db\exception\DbException as Exception;
/**
* Db Base Builder.
*/
abstract class BaseBuilder
{
/**
* Connection对象
*
* @var ConnectionInterface
*/
protected $connection;
/**
* 查询表达式映射.
*
* @var array
*/
protected $exp = ['NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME'];
/**
* 查询表达式解析.
*
* @var array
*/
protected $parser = [
'parseCompare' => ['=', '<>', '>', '>=', '<', '<='],
'parseLike' => ['LIKE', 'NOT LIKE'],
'parseBetween' => ['NOT BETWEEN', 'BETWEEN'],
'parseIn' => ['NOT IN', 'IN'],
'parseExp' => ['EXP'],
'parseNull' => ['NOT NULL', 'NULL'],
'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'],
'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'],
'parseExists' => ['NOT EXISTS', 'EXISTS'],
'parseColumn' => ['COLUMN'],
];
/**
* SELECT SQL表达式.
*
* @var string
*/
protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%';
/**
* INSERT SQL表达式.
*
* @var string
*/
protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
/**
* INSERT ALL SQL表达式.
*
* @var string
*/
protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
/**
* UPDATE SQL表达式.
*
* @var string
*/
protected $updateSql = 'UPDATE%EXTRA% %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
/**
* DELETE SQL表达式.
*
* @var string
*/
protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
/**
* 架构函数.
*
* @param ConnectionInterface $connection 数据库连接对象实例
*/
public function __construct(ConnectionInterface $connection)
{
$this->connection = $connection;
}
/**
* 获取当前的连接对象实例.
*
* @return ConnectionInterface
*/
public function getConnection(): ConnectionInterface
{
return $this->connection;
}
/**
* 注册查询表达式解析.
*
* @param string $name 解析方法
* @param array $parser 匹配表达式数据
*
* @return $this
*/
public function bindParser(string $name, array $parser)
{
$this->parser[$name] = $parser;
return $this;
}
/**
* 数据分析.
*
* @param Query $query 查询对象
* @param array $data 数据
* @param array $fields 字段信息
* @param array $bind 参数绑定
*
* @return array
*/
abstract protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array;
/**
* 数据绑定处理.
*
* @param Query $query 查询对象
* @param string $key 字段名
* @param mixed $data 数据
* @param array $bind 绑定数据
*
* @return string
*/
abstract protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string;
/**
* 字段名分析.
*
* @param Query $query 查询对象
* @param mixed $key 字段名
* @param bool $strict 严格检测
*
* @return string
*/
abstract public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string;
/**
* 查询额外参数分析.
*
* @param Query $query 查询对象
* @param string $extra 额外参数
*
* @return string
*/
abstract protected function parseExtra(Query $query, string $extra): string;
/**
* field分析.
*
* @param Query $query 查询对象
* @param array $fields 字段名
*
* @return string
*/
abstract protected function parseField(Query $query, array $fields): string;
/**
* table分析.
*
* @param Query $query 查询对象
* @param array|string $tables 表名
*
* @return string
*/
abstract protected function parseTable(Query $query, array|string $tables): string;
/**
* where分析.
*
* @param Query $query 查询对象
* @param array $where 查询条件
*
* @return string
*/
abstract protected function parseWhere(Query $query, array $where): string;
/**
* 生成查询条件SQL.
*
* @param Query $query 查询对象
* @param array $where 查询条件
*
* @return string
*/
public function buildWhere(Query $query, array $where): string
{
if (empty($where)) {
$where = [];
}
$whereStr = '';
$binds = $query->getFieldsBindType();
foreach ($where as $logic => $val) {
$str = $this->parseWhereLogic($query, $logic, $val, $binds);
$whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str);
}
return $whereStr;
}
/**
* 不同字段使用相同查询条件(AND).
*
* @param Query $query 查询对象
* @param string $logic Logic
* @param array $val 查询条件
* @param array $binds 参数绑定
*
* @return array
*/
protected function parseWhereLogic(Query $query, string $logic, array $val, array $binds = []): array
{
$where = [];
foreach ($val as $value) {
if ($value instanceof Raw) {
$where[] = ' ' . $logic . ' ( ' . $this->parseRaw($query, $value) . ' )';
continue;
}
if (is_array($value)) {
if (key($value) !== 0) {
throw new Exception('where express error:' . var_export($value, true));
}
$field = array_shift($value);
} elseif (true === $value) {
$where[] = ' ' . $logic . ' 1 ';
continue;
} elseif (!($value instanceof Closure)) {
throw new Exception('where express error:' . var_export($value, true));
}
if ($value instanceof Closure) {
// 使用闭包查询
$whereClosureStr = $this->parseClosureWhere($query, $value, $logic);
if ($whereClosureStr) {
$where[] = $whereClosureStr;
}
} elseif (is_array($field)) {
$where[] = $this->parseMultiWhereField($query, $value, $field, $logic, $binds);
} elseif ($field instanceof Raw) {
$where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds);
} elseif (str_contains($field, '|')) {
$where[] = $this->parseFieldsOr($query, $value, $field, $logic, $binds);
} elseif (str_contains($field, '&')) {
$where[] = $this->parseFieldsAnd($query, $value, $field, $logic, $binds);
} else {
// 对字段使用表达式查询
$field = is_string($field) ? $field : '';
$where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds);
}
}
return $where;
}
/**
* 不同字段使用相同查询条件(AND).
*
* @param Query $query 查询对象
* @param mixed $value 查询条件
* @param string $field 查询字段
* @param string $logic Logic
* @param array $binds 参数绑定
*
* @return string
*/
protected function parseFieldsAnd(Query $query, $value, string $field, string $logic, array $binds): string
{
$item = [];
foreach (explode('&', $field) as $k) {
$item[] = $this->parseWhereItem($query, $k, $value, $binds);
}
return ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )';
}
/**
* 不同字段使用相同查询条件(OR).
*
* @param Query $query 查询对象
* @param array $value 查询条件
* @param string $field 查询字段
* @param string $logic Logic
* @param array $binds 参数绑定
*
* @return string
*/
protected function parseFieldsOr(Query $query, array $value, string $field, string $logic, array $binds): string
{
$item = [];
foreach (explode('|', $field) as $k) {
$item[] = $this->parseWhereItem($query, $k, $value, $binds);
}
return ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )';
}
/**
* 闭包查询.
*
* @param Query $query 查询对象
* @param Closure $value 查询条件
* @param string $logic Logic
*
* @return string
*/
protected function parseClosureWhere(Query $query, Closure $value, string $logic): string
{
$newQuery = $query->newQuery();
$value($newQuery);
$whereClosure = $this->buildWhere($newQuery, $newQuery->getOptions('where') ?: []);
if (!empty($whereClosure)) {
$query->bind($newQuery->getBind(false));
$where = ' ' . $logic . ' ( ' . $whereClosure . ' )';
}
return $where ?? '';
}
/**
* 复合条件查询.
*
* @param Query $query 查询对象
* @param mixed $value 查询条件
* @param mixed $field 查询字段
* @param string $logic Logic
* @param array $binds 参数绑定
*
* @return string
*/
protected function parseMultiWhereField(Query $query, array $value, $field, string $logic, array $binds): string
{
array_unshift($value, $field);
$where = [];
foreach ($value as $item) {
$where[] = $this->parseWhereItem($query, array_shift($item), $item, $binds);
}
return ' ' . $logic . ' ( ' . implode(' AND ', $where) . ' )';
}
/**
* where子单元分析.
*
* @param Query $query 查询对象
* @param mixed $field 查询字段
* @param array $val 查询条件
* @param array $binds 参数绑定
*
* @return string
*/
abstract protected function parseWhereItem(Query $query, $field, array $val, array $binds = []): string;
/**
* 模糊查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param array $value
* @param string $field
* @param int $bindType
* @param string $logic
*
* @return string
*/
abstract protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string;
/**
* 表达式查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param Raw $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string
{
// 表达式查询
return '( ' . $key . ' ' . $this->parseRaw($query, $value) . ' )';
}
/**
* 表达式查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param array $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseColumn(Query $query, string $key, $exp, array $value, string $field, int $bindType): string
{
// 字段比较查询
[$op, $field] = $value;
if (!in_array(trim($op), ['=', '<>', '>', '>=', '<', '<='])) {
throw new Exception('where express error:' . var_export($value, true));
}
return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field, true) . ' )';
}
/**
* Null查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
* @param int $bindType
*
* @return string
*/
abstract protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string;
/**
* 范围查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
* @param int $bindType
*
* @return string
*/
abstract protected function parseBetween(Query $query, string $key, string $exp, array|string $value, $field, int $bindType): string;
/**
* Exists查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param Raw|Closure $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseExists(Query $query, string $key, string $exp, Raw|Closure $value, string $field, int $bindType): string
{
// EXISTS 查询
if ($value instanceof Closure) {
$value = $this->parseClosure($query, $value, false);
} elseif ($value instanceof Raw) {
$value = $this->parseRaw($query, $value);
}
return $exp . ' ( ' . $value . ' )';
}
/**
* 时间比较查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string
{
return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType);
}
/**
* 大小比较查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseCompare(Query $query, string $key, string $exp, $value, $field, int $bindType): string
{
if (is_array($value)) {
throw new Exception('where express error:' . $exp . var_export($value, true));
}
// 比较运算
if ($value instanceof Closure) {
$value = $this->parseClosure($query, $value);
} elseif ($value instanceof Raw) {
$value = $this->parseRaw($query, $value);
}
if ('=' == $exp && is_null($value)) {
return $key . ' IS NULL';
}
return $key . ' ' . $exp . ' ' . $value;
}
/**
* 时间范围查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseBetweenTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string
{
if (is_string($value)) {
$value = explode(',', $value);
}
return $key . ' ' . substr($exp, 0, -4)
. $this->parseDateTime($query, $value[0], $field, $bindType)
. ' AND '
. $this->parseDateTime($query, $value[1], $field, $bindType);
}
/**
* IN查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
* @param int $bindType
*
* @return string
*/
abstract protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string;
/**
* 闭包子查询.
*
* @param Query $query 查询对象
* @param Closure $call
* @param bool $show
*
* @return string
*/
protected function parseClosure(Query $query, Closure $call, bool $show = true): string
{
$newQuery = $query->newQuery()->removeOption();
$call($newQuery);
return $newQuery->buildSql($show);
}
/**
* 日期时间条件解析.
*
* @param Query $query 查询对象
* @param mixed $value
* @param string $key
* @param int $bindType
*
* @return string
*/
abstract protected function parseDateTime(Query $query, $value, string $key, int $bindType): string;
/**
* limit分析.
*
* @param Query $query 查询对象
* @param mixed $limit
*
* @return string
*/
abstract protected function parseLimit(Query $query, string $limit): string;
/**
* join分析.
*
* @param Query $query 查询对象
* @param array $join
*
* @return string
*/
abstract protected function parseJoin(Query $query, array $join): string;
/**
* order分析.
*
* @param Query $query 查询对象
* @param array $order
*
* @return string
*/
abstract protected function parseOrder(Query $query, array $order): string;
/**
* 分析Raw对象
*
* @param Query $query 查询对象
* @param Raw $raw Raw对象
*
* @return string
*/
abstract protected function parseRaw(Query $query, Raw $raw): string;
/**
* 随机排序.
*
* @param Query $query 查询对象
*
* @return string
*/
abstract protected function parseRand(Query $query): string;
/**
* group分析.
*
* @param Query $query 查询对象
* @param mixed $group
*
* @return string
*/
abstract protected function parseGroup(Query $query, string|array $group): string;
/**
* having分析.
*
* @param Query $query 查询对象
* @param string $having
*
* @return string
*/
abstract protected function parseHaving(Query $query, string $having): string;
/**
* comment分析.
*
* @param Query $query 查询对象
* @param string $comment
*
* @return string
*/
protected function parseComment(Query $query, string $comment): string
{
if (str_contains($comment, '*/')) {
$comment = strstr($comment, '*/', true);
}
return !empty($comment) ? ' /* ' . $comment . ' */' : '';
}
/**
* distinct分析.
*
* @param Query $query 查询对象
* @param mixed $distinct
*
* @return string
*/
abstract protected function parseDistinct(Query $query, bool $distinct): string;
/**
* union分析.
*
* @param Query $query 查询对象
* @param array $union
*
* @return string
*/
protected function parseUnion(Query $query, array $union): string
{
if (empty($union)) {
return '';
}
$type = $union['type'];
unset($union['type']);
foreach ($union as $u) {
if ($u instanceof Closure) {
$sql[] = $type . ' ' . $this->parseClosure($query, $u);
} elseif (is_string($u)) {
$sql[] = $type . ' ( ' . $u . ' )';
}
}
return ' ' . implode(' ', $sql);
}
/**
* index分析,可在操作链中指定需要强制使用的索引.
*
* @param Query $query 查询对象
* @param mixed $index
*
* @return string
*/
abstract protected function parseForce(Query $query, string|array $index): string;
/**
* 设置锁机制.
*
* @param Query $query 查询对象
* @param bool|string $lock
*
* @return string
*/
abstract protected function parseLock(Query $query, bool|string $lock = false): string;
/**
* 生成查询SQL.
*
* @param Query $query 查询对象
* @param bool $one 是否仅获取一个记录
*
* @return string
*/
public function select(Query $query, bool $one = false): string
{
$options = $query->getOptions();
return str_replace(
['%TABLE%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
[
$this->parseTable($query, $options['table']),
$this->parseDistinct($query, $options['distinct']),
$this->parseExtra($query, $options['extra']),
$this->parseField($query, $options['field'] ?? []),
$this->parseJoin($query, $options['join']),
$this->parseWhere($query, $options['where']),
$this->parseGroup($query, $options['group']),
$this->parseHaving($query, $options['having']),
$this->parseOrder($query, $options['order']),
$this->parseLimit($query, $one ? '1' : $options['limit']),
$this->parseUnion($query, $options['union']),
$this->parseLock($query, $options['lock']),
$this->parseComment($query, $options['comment']),
$this->parseForce($query, $options['force']),
],
$this->selectSql
);
}
/**
* 生成Insert SQL.
*
* @param Query $query 查询对象
*
* @return string
*/
public function insert(Query $query): string
{
$options = $query->getOptions();
// 分析并处理数据
$data = $this->parseData($query, $options['data']);
if (empty($data)) {
return '';
}
$fields = array_keys($data);
$values = array_values($data);
return str_replace(
['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
[
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
$this->parseTable($query, $options['table']),
$this->parseExtra($query, $options['extra']),
implode(' , ', $fields),
implode(' , ', $values),
$this->parseComment($query, $options['comment']),
],
$this->insertSql
);
}
/**
* 生成insertall SQL.
*
* @param Query $query 查询对象
* @param array $dataSet 数据集
*
* @return string
*/
public function insertAll(Query $query, array $dataSet): string
{
$options = $query->getOptions();
// 获取绑定信息
$bind = $query->getFieldsBindType();
// 获取合法的字段
if (empty($options['field']) || '*' == $options['field']) {
$allowFields = array_keys($bind);
} else {
$allowFields = $options['field'];
}
$fields = [];
$values = [];
foreach ($dataSet as $k => $data) {
$data = $this->parseData($query, $data, $allowFields, $bind);
$values[] = 'SELECT ' . implode(',', array_values($data));
if (!isset($insertFields)) {
$insertFields = array_keys($data);
}
}
foreach ($insertFields as $field) {
$fields[] = $this->parseKey($query, $field);
}
return str_replace(
['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
[
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
$this->parseTable($query, $options['table']),
$this->parseExtra($query, $options['extra']),
implode(' , ', $fields),
implode(' UNION ALL ', $values),
$this->parseComment($query, $options['comment']),
],
$this->insertAllSql
);
}
/**
* 生成slect insert SQL.
*
* @param Query $query 查询对象
* @param array $fields 数据
* @param string $table 数据表
*
* @return string
*/
public function selectInsert(Query $query, array $fields, string $table): string
{
foreach ($fields as &$field) {
$field = $this->parseKey($query, $field, true);
}
return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query);
}
/**
* 生成update SQL.
*
* @param Query $query 查询对象
*
* @return string
*/
public function update(Query $query): string
{
$options = $query->getOptions();
$data = $this->parseData($query, $options['data']);
if (empty($data)) {
return '';
}
$set = [];
foreach ($data as $key => $val) {
$set[] = $key . ' = ' . $val;
}
return str_replace(
['%TABLE%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
[
$this->parseTable($query, $options['table']),
$this->parseExtra($query, $options['extra']),
implode(' , ', $set),
$this->parseJoin($query, $options['join']),
$this->parseWhere($query, $options['where']),
$this->parseOrder($query, $options['order']),
$this->parseLimit($query, $options['limit']),
$this->parseLock($query, $options['lock']),
$this->parseComment($query, $options['comment']),
],
$this->updateSql
);
}
/**
* 生成delete SQL.
*
* @param Query $query 查询对象
*
* @return string
*/
public function delete(Query $query): string
{
$options = $query->getOptions();
return str_replace(
['%TABLE%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
[
$this->parseTable($query, $options['table']),
$this->parseExtra($query, $options['extra']),
!empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '',
$this->parseJoin($query, $options['join']),
$this->parseWhere($query, $options['where']),
$this->parseOrder($query, $options['order']),
$this->parseLimit($query, $options['limit']),
$this->parseLock($query, $options['lock']),
$this->parseComment($query, $options['comment']),
],
$this->deleteSql
);
}
}
File diff suppressed because it is too large Load Diff
+774
View File
@@ -0,0 +1,774 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db;
use Closure;
use Stringable;
use think\db\BaseQuery as Query;
use think\db\exception\DbException as Exception;
/**
* Db Builder.
*/
class Builder extends BaseBuilder
{
/**
* 数据分析.
*
* @param Query $query 查询对象
* @param array $data 数据
* @param array $fields 字段信息
* @param array $bind 参数绑定
*
* @return array
*/
protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array
{
if (empty($data)) {
return [];
}
$options = $query->getOptions();
// 获取绑定信息
if (empty($bind)) {
$bind = $query->getFieldsBindType();
}
if (empty($fields)) {
if (empty($options['field']) || '*' == $options['field']) {
$fields = array_keys($bind);
} else {
$fields = $options['field'];
}
}
$result = [];
foreach ($data as $key => $val) {
$item = $this->parseKey($query, $key, true);
if ($val instanceof Raw) {
$result[$item] = $this->parseRaw($query, $val);
continue;
} elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $query->getFieldType($key))) {
$val = json_encode($val);
}
if (str_contains($key, '->')) {
[$key, $name] = explode('->', $key, 2);
$item = $this->parseKey($query, $key);
$result[$item . '->' . $name] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key . '->' . $name, $val, $bind) . ')';
} elseif (!str_contains($key, '.') && !in_array($key, $fields, true)) {
if ($options['strict']) {
throw new Exception('fields not exists:[' . $key . ']');
}
} elseif (is_null($val)) {
$result[$item] = 'NULL';
} elseif (is_array($val) && !empty($val) && is_string($val[0])) {
if (in_array(strtoupper($val[0]), ['INC', 'DEC'])) {
$result[$item] = match (strtoupper($val[0])) {
'INC' => $item . ' + ' . floatval($val[1]),
'DEC' => $item . ' - ' . floatval($val[1]),
};
}
} elseif (is_scalar($val)) {
// 过滤非标量数据
if (!$query->isAutoBind() && Connection::PARAM_STR == $bind[$key]) {
$val = '\'' . $val . '\'';
}
$result[$item] = !$query->isAutoBind() ? $val : $this->parseDataBind($query, $key, $val, $bind);
}
}
return $result;
}
/**
* 数据绑定处理.
*
* @param Query $query 查询对象
* @param string $key 字段名
* @param mixed $data 数据
* @param array $bind 绑定数据
*
* @return string
*/
protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string
{
if ($data instanceof Raw) {
return $this->parseRaw($query, $data);
}
$name = $query->bindValue($data, $bind[$key] ?? Connection::PARAM_STR);
return ':' . $name;
}
/**
* 字段名分析.
*
* @param Query $query 查询对象
* @param mixed $key 字段名
* @param bool $strict 严格检测
*
* @return string
*/
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
{
return $key;
}
/**
* 查询额外参数分析.
*
* @param Query $query 查询对象
* @param string $extra 额外参数
*
* @return string
*/
protected function parseExtra(Query $query, string $extra): string
{
return preg_match('/^[\w]+$/i', $extra) ? ' ' . strtoupper($extra) : '';
}
/**
* field分析.
*
* @param Query $query 查询对象
* @param array $fields 字段名
*
* @return string
*/
protected function parseField(Query $query, array $fields): string
{
if (!empty($fields)) {
// 支持 'field1'=>'field2' 这样的字段别名定义
$array = [];
foreach ($fields as $key => $field) {
if ($field instanceof Raw) {
$array[] = $this->parseRaw($query, $field);
} elseif (!is_numeric($key)) {
$array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true);
} else {
$array[] = $this->parseKey($query, $field);
}
}
$fieldsStr = implode(',', $array);
} else {
$fieldsStr = '*';
}
return $fieldsStr;
}
/**
* table分析.
*
* @param Query $query 查询对象
* @param array|string $tables 表名
*
* @return string
*/
protected function parseTable(Query $query, array|string $tables): string
{
$item = [];
$options = $query->getOptions();
foreach ((array) $tables as $key => $table) {
if ($table instanceof Raw) {
$item[] = $this->parseRaw($query, $table);
} elseif (!is_numeric($key)) {
$item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table);
} elseif (isset($options['alias'][$table])) {
$item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]);
} else {
$item[] = $this->parseKey($query, $table);
}
}
return implode(',', $item);
}
/**
* where分析.
*
* @param Query $query 查询对象
* @param array $where 查询条件
*
* @return string
*/
protected function parseWhere(Query $query, array $where): string
{
$options = $query->getOptions();
$whereStr = $this->buildWhere($query, $where);
if (!empty($options['soft_delete'])) {
// 附加软删除条件
[$field, $condition] = $options['soft_delete'];
$binds = $query->getFieldsBindType();
$whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : '';
$whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, $binds);
}
return empty($whereStr) ? '' : ' WHERE ' . $whereStr;
}
/**
* where子单元分析.
*
* @param Query $query 查询对象
* @param mixed $field 查询字段
* @param array $val 查询条件
* @param array $binds 参数绑定
*
* @return string
*/
protected function parseWhereItem(Query $query, $field, array $val, array $binds = []): string
{
// 字段分析
$key = $field ? $this->parseKey($query, $field, true) : '';
[$exp, $value] = $val;
// 检测操作符
if (!is_string($exp)) {
throw new Exception('where express error:' . var_export($exp, true));
}
$exp = strtoupper($exp);
if (isset($this->exp[$exp])) {
$exp = $this->exp[$exp];
}
if (is_string($field) && 'LIKE' != $exp) {
$bindType = $binds[$field] ?? Connection::PARAM_STR;
} else {
$bindType = Connection::PARAM_STR;
}
if ($value instanceof Raw) {
} elseif ($value instanceof Stringable) {
// 对象数据写入
$value = $value->__toString();
}
if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && !str_contains($exp, 'TIME')) {
if (is_string($value) && str_starts_with($value, ':') && $query->isBind(substr($value, 1))) {
} else {
$name = $query->bindValue($value, $bindType);
$value = ':' . $name;
}
}
// 解析查询表达式
foreach ($this->parser as $fun => $parse) {
if (in_array($exp, $parse)) {
return $this->$fun($query, $key, $exp, $value, $field, $bindType, $val[2] ?? 'AND');
}
}
throw new Exception('where express error:' . $exp);
}
/**
* 模糊查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param array $value
* @param string $field
* @param int $bindType
* @param string $logic
*
* @return string
*/
protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string
{
// 模糊匹配
if (is_array($value)) {
$array = [];
foreach ($value as $item) {
$name = $query->bindValue($item, Connection::PARAM_STR);
$array[] = $key . ' ' . $exp . ' :' . $name;
}
$whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')';
} else {
$whereStr = $key . ' ' . $exp . ' ' . $value;
}
return $whereStr;
}
/**
* 表达式查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param Raw $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string
{
// 表达式查询
return '( ' . $key . ' ' . $this->parseRaw($query, $value) . ' )';
}
/**
* Null查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string
{
// NULL 查询
return $key . ' IS ' . $exp;
}
/**
* 范围查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseBetween(Query $query, string $key, string $exp, array|string $value, $field, int $bindType): string
{
// BETWEEN 查询
$data = is_array($value) ? $value : explode(',', $value);
$min = $query->bindValue($data[0], $bindType);
$max = $query->bindValue($data[1], $bindType);
return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' ';
}
/**
* IN查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string
{
// IN 查询
if ($value instanceof Closure) {
$value = $this->parseClosure($query, $value, false);
} elseif ($value instanceof Raw) {
$value = $this->parseRaw($query, $value);
} else {
$value = array_unique(is_array($value) ? $value : explode(',', (string) $value));
if (count($value) === 0) {
return 'IN' == $exp ? '0 = 1' : '1 = 1';
}
if ($query->isAutoBind()) {
$array = [];
foreach ($value as $v) {
$name = $query->bindValue($v, $bindType);
$array[] = ':' . $name;
}
$value = implode(',', $array);
} elseif (Connection::PARAM_STR == $bindType) {
$value = '\'' . implode('\',\'', $value) . '\'';
} else {
$value = implode(',', $value);
}
if (!str_contains($value, ',')) {
return $key . ('IN' == $exp ? ' = ' : ' <> ') . $value;
}
}
return $key . ' ' . $exp . ' (' . $value . ')';
}
/**
* 日期时间条件解析.
*
* @param Query $query 查询对象
* @param mixed $value
* @param string $key
* @param int $bindType
*
* @return string
*/
protected function parseDateTime(Query $query, $value, string $key, int $bindType): string
{
$options = $query->getOptions();
// 获取时间字段类型
if (str_contains($key, '.')) {
[$table, $key] = explode('.', $key);
if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) {
$table = $pos;
}
} else {
$table = $options['table'];
}
$type = $query->getFieldType($key);
if ($type) {
if (is_string($value)) {
$value = strtotime($value) ?: $value;
}
if (is_int($value)) {
if (preg_match('/(datetime|timestamp)/is', $type)) {
// 日期及时间戳类型
$value = date('Y-m-d H:i:s', $value);
} elseif (preg_match('/(date)/is', $type)) {
// 日期及时间戳类型
$value = date('Y-m-d', $value);
}
}
}
$name = $query->bindValue($value, $bindType);
return ':' . $name;
}
/**
* limit分析.
*
* @param Query $query 查询对象
* @param mixed $limit
*
* @return string
*/
protected function parseLimit(Query $query, string $limit): string
{
return (!empty($limit) && !str_contains($limit, '(')) ? ' LIMIT ' . $limit . ' ' : '';
}
/**
* join分析.
*
* @param Query $query 查询对象
* @param array $join
*
* @return string
*/
protected function parseJoin(Query $query, array $join): string
{
$joinStr = '';
foreach ($join as $item) {
[$table, $type, $on] = $item;
if (str_contains($on, '=')) {
[$val1, $val2] = explode('=', $on, 2);
$condition = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2);
} else {
$condition = $on;
}
$table = $this->parseTable($query, $table);
$joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . $condition;
}
return $joinStr;
}
/**
* order分析.
*
* @param Query $query 查询对象
* @param array $order
*
* @return string
*/
protected function parseOrder(Query $query, array $order): string
{
$array = [];
foreach ($order as $key => $val) {
if ($val instanceof Raw) {
$array[] = $this->parseRaw($query, $val);
} elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) {
$array[] = $this->parseOrderField($query, $key, $val);
} elseif ('[rand]' == $val) {
$array[] = $this->parseRand($query);
} elseif (is_string($val)) {
if (is_numeric($key)) {
[$key, $sort] = explode(' ', str_contains($val, ' ') ? $val : $val . ' ');
} else {
$sort = $val;
}
if (preg_match('/^[\w\.]+$/', $key)) {
$sort = strtoupper($sort);
$sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
$array[] = $this->parseKey($query, $key, true) . $sort;
} else {
throw new Exception('order express error:' . $key);
}
}
}
return empty($array) ? '' : ' ORDER BY ' . implode(',', $array);
}
/**
* 分析Raw对象
*
* @param Query $query 查询对象
* @param Raw $raw Raw对象
*
* @return string
*/
protected function parseRaw(Query $query, Raw $raw): string
{
$sql = $raw->getValue();
$bind = $raw->getBind();
if ($bind) {
$query->bindParams($sql, $bind);
}
return $sql;
}
/**
* 随机排序.
*
* @param Query $query 查询对象
*
* @return string
*/
protected function parseRand(Query $query): string
{
return '';
}
/**
* orderField分析.
*
* @param Query $query 查询对象
* @param string $key
* @param array $val
*
* @return string
*/
protected function parseOrderField(Query $query, string $key, array $val): string
{
if (isset($val['sort'])) {
$sort = $val['sort'];
unset($val['sort']);
} else {
$sort = '';
}
$sort = strtoupper($sort);
$sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
$bind = $query->getFieldsBindType();
foreach ($val as $k => $item) {
$val[$k] = $this->parseDataBind($query, $key, $item, $bind);
}
return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort;
}
/**
* group分析.
*
* @param Query $query 查询对象
* @param mixed $group
*
* @return string
*/
protected function parseGroup(Query $query, string|array $group): string
{
if (empty($group)) {
return '';
}
if (is_string($group)) {
$group = explode(',', $group);
}
$val = [];
foreach ($group as $key) {
$val[] = $this->parseKey($query, $key);
}
return ' GROUP BY ' . implode(',', $val);
}
/**
* having分析.
*
* @param Query $query 查询对象
* @param string $having
*
* @return string
*/
protected function parseHaving(Query $query, string $having): string
{
return !empty($having) ? ' HAVING ' . $having : '';
}
/**
* comment分析.
*
* @param Query $query 查询对象
* @param string $comment
*
* @return string
*/
protected function parseComment(Query $query, string $comment): string
{
if (str_contains($comment, '*/')) {
$comment = strstr($comment, '*/', true);
}
return !empty($comment) ? ' /* ' . $comment . ' */' : '';
}
/**
* distinct分析.
*
* @param Query $query 查询对象
* @param mixed $distinct
*
* @return string
*/
protected function parseDistinct(Query $query, bool $distinct): string
{
return !empty($distinct) ? ' DISTINCT ' : '';
}
/**
* index分析,可在操作链中指定需要强制使用的索引.
*
* @param Query $query 查询对象
* @param mixed $index
*
* @return string
*/
protected function parseForce(Query $query, string|array $index): string
{
if (empty($index)) {
return '';
}
if (is_array($index)) {
$index = implode(',', $index);
}
return sprintf(' FORCE INDEX ( %s ) ', $index);
}
/**
* 设置锁机制.
*
* @param Query $query 查询对象
* @param bool|string $lock
*
* @return string
*/
protected function parseLock(Query $query, bool|string $lock = false): string
{
if (is_bool($lock)) {
return $lock ? ' FOR UPDATE ' : '';
}
if (is_string($lock) && !empty($lock)) {
return ' ' . trim($lock) . ' ';
}
return '';
}
/**
* 生成insertall SQL.
*
* @param Query $query 查询对象
* @param array $keys 字段名
* @param array $datas 数据
*
* @return string
*/
public function insertAllByKeys(Query $query, array $keys, array $datas): string
{
$options = $query->getOptions();
// 获取绑定信息
$bind = $query->getFieldsBindType();
$fields = [];
$values = [];
foreach ($keys as $field) {
$fields[] = $this->parseKey($query, $field);
}
foreach ($datas as $k => $data) {
foreach ($data as $key => &$val) {
if (!$query->isAutoBind()) {
$val = Connection::PARAM_STR == $bind[$keys[$key]] ? '\'' . $val . '\'' : $val;
} else {
$val = $this->parseDataBind($query, $keys[$key], $val, $bind);
}
}
$values[] = 'SELECT ' . implode(',', $data);
}
return str_replace(
['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
[
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
$this->parseTable($query, $options['table']),
$this->parseExtra($query, $options['extra']),
implode(' , ', $fields),
implode(' UNION ALL ', $values),
$this->parseComment($query, $options['comment']),
],
$this->insertAllSql
);
}
}
+220
View File
@@ -0,0 +1,220 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db;
use DateInterval;
use DateTime;
use DateTimeInterface;
use think\db\exception\InvalidArgumentException;
/**
* CacheItem实现类.
*/
class CacheItem
{
/**
* 缓存Key.
*
* @var string
*/
protected $key;
/**
* 缓存内容.
*
* @var mixed
*/
protected $value;
/**
* 过期时间.
*
* @var int|DateTimeInterface
*/
protected $expire;
/**
* 缓存tag.
*
* @var string
*/
protected $tag;
/**
* 缓存是否命中.
*
* @var bool
*/
protected $isHit = false;
public function __construct(string $key = null)
{
$this->key = $key;
}
/**
* 为此缓存项设置「键」.
*
* @param string $key
*
* @return $this
*/
public function setKey(string $key)
{
$this->key = $key;
return $this;
}
/**
* 返回当前缓存项的「键」.
*
* @return string
*/
public function getKey()
{
return $this->key;
}
/**
* 返回当前缓存项的有效期
*
* @return DateTimeInterface|int|null
*/
public function getExpire()
{
if ($this->expire instanceof DateTimeInterface) {
return $this->expire;
}
return $this->expire ? $this->expire - time() : null;
}
/**
* 获取缓存Tag.
*
* @return string|array
*/
public function getTag()
{
return $this->tag;
}
/**
* 凭借此缓存项的「键」从缓存系统里面取出缓存项.
*
* @return mixed
*/
public function get()
{
return $this->value;
}
/**
* 确认缓存项的检查是否命中.
*
* @return bool
*/
public function isHit(): bool
{
return $this->isHit;
}
/**
* 为此缓存项设置「值」.
*
* @param mixed $value
*
* @return $this
*/
public function set($value)
{
$this->value = $value;
$this->isHit = true;
return $this;
}
/**
* 为此缓存项设置所属标签.
*
* @param string|array $tag
*
* @return $this
*/
public function tag($tag = null)
{
$this->tag = $tag;
return $this;
}
/**
* 设置缓存项的有效期
*
* @param mixed $expire
*
* @return $this
*/
public function expire($expire)
{
if (is_null($expire)) {
$this->expire = null;
} elseif (is_numeric($expire) || $expire instanceof DateInterval) {
$this->expiresAfter($expire);
} elseif ($expire instanceof DateTimeInterface) {
$this->expire = $expire;
} else {
throw new InvalidArgumentException('not support datetime');
}
return $this;
}
/**
* 设置缓存项的准确过期时间点.
*
* @param DateTimeInterface $expiration
*
* @return $this
*/
public function expiresAt(DateTimeInterface $expiration)
{
$this->expire = $expiration;
return $this;
}
/**
* 设置缓存项的过期时间.
*
* @param int|DateInterval $timeInterval
*
* @throws InvalidArgumentException
*
* @return $this
*/
public function expiresAfter($timeInterval)
{
if ($timeInterval instanceof DateInterval) {
$this->expire = (int) DateTime::createFromFormat('U', (string) time())->add($timeInterval)->format('U');
} elseif (is_numeric($timeInterval)) {
$this->expire = $timeInterval + time();
} else {
throw new InvalidArgumentException('not support datetime');
}
return $this;
}
}
+406
View File
@@ -0,0 +1,406 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db;
use Psr\SimpleCache\CacheInterface;
use think\DbManager;
/**
* 数据库连接基础类.
*/
abstract class Connection implements ConnectionInterface
{
const PARAM_INT = 1;
const PARAM_STR = 2;
const PARAM_BOOL = 5;
const PARAM_FLOAT = 21;
/**
* 当前SQL指令.
*
* @var string
*/
protected $queryStr = '';
/**
* 返回或者影响记录数.
*
* @var int
*/
protected $numRows = 0;
/**
* 事务指令数.
*
* @var int
*/
protected $transTimes = 0;
/**
* 错误信息.
*
* @var string
*/
protected $error = '';
/**
* 数据库连接ID 支持多个连接.
*
* @var array
*/
protected $links = [];
/**
* 当前连接ID.
*
* @var object
*/
protected $linkID;
/**
* 当前读连接ID.
*
* @var object
*/
protected $linkRead;
/**
* 当前写连接ID.
*
* @var object
*/
protected $linkWrite;
/**
* 数据表信息.
*
* @var array
*/
protected $info = [];
/**
* 查询开始时间.
*
* @var float
*/
protected $queryStartTime;
/**
* Builder对象
*
* @var Builder
*/
protected $builder;
/**
* Db对象
*
* @var DbManager
*/
protected $db;
/**
* 是否读取主库.
*
* @var bool
*/
protected $readMaster = false;
/**
* 数据库连接参数配置.
*
* @var array
*/
protected $config = [];
/**
* 缓存对象
*
* @var CacheInterface
*/
protected $cache;
/**
* 架构函数 读取数据库配置信息.
*
* @param array $config 数据库配置数组
*/
public function __construct(array $config = [])
{
if (!empty($config)) {
$this->config = array_merge($this->config, $config);
}
// 创建Builder对象
$class = $this->getBuilderClass();
$this->builder = new $class($this);
}
/**
* 获取当前的builder实例对象
*
* @return Builder
*/
public function getBuilder()
{
return $this->builder;
}
/**
* 创建查询对象
*/
public function newQuery()
{
$class = $this->getQueryClass();
/** @var BaseQuery $query */
$query = new $class($this);
$timeRule = $this->db->getConfig('time_query_rule');
if (!empty($timeRule)) {
$query->timeRule($timeRule);
}
return $query;
}
/**
* 指定表名开始查询.
*
* @param $table
*
* @return BaseQuery
*/
public function table($table)
{
return $this->newQuery()->table($table);
}
/**
* 指定表名开始查询(不带前缀).
*
* @param $name
*
* @return BaseQuery
*/
public function name($name)
{
return $this->newQuery()->name($name);
}
/**
* 设置当前的数据库Db对象
*
* @param DbManager $db
*
* @return void
*/
public function setDb(DbManager $db)
{
$this->db = $db;
}
/**
* 设置当前的缓存对象
*
* @param CacheInterface $cache
*
* @return void
*/
public function setCache(CacheInterface $cache)
{
$this->cache = $cache;
}
/**
* 获取当前的缓存对象
*
* @return CacheInterface|null
*/
public function getCache()
{
return $this->cache;
}
/**
* 获取数据库的配置参数.
*
* @param string $config 配置名称
*
* @return mixed
*/
public function getConfig(string $config = '')
{
if ('' === $config) {
return $this->config;
}
return $this->config[$config] ?? null;
}
/**
* 数据库SQL监控.
*
* @param string $sql 执行的SQL语句 留空自动获取
* @param bool $master 主从标记
*
* @return void
*/
protected function trigger(string $sql = '', bool $master = false): void
{
$listen = $this->db->getListen();
if (empty($listen)) {
$listen[] = function ($sql, $time, $master) {
if (str_starts_with($sql, 'CONNECT:')) {
$this->db->log($sql);
return;
}
// 记录SQL
if (is_bool($master)) {
// 分布式记录当前操作的主从
$master = $master ? 'master|' : 'slave|';
} else {
$master = '';
}
$this->db->log($sql . ' [ ' . $master . 'RunTime:' . $time . 's ]');
};
}
$runtime = number_format((microtime(true) - $this->queryStartTime), 6);
$sql = $sql ?: $this->getLastsql();
if (empty($this->config['deploy'])) {
$master = null;
}
foreach ($listen as $callback) {
if (is_callable($callback)) {
$callback($sql, $runtime, $master);
}
}
}
/**
* 缓存数据.
*
* @param CacheItem $cacheItem 缓存Item
*/
protected function cacheData(CacheItem $cacheItem)
{
if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) {
$this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
} else {
$this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
}
}
/**
* 分析缓存Key.
*
* @param BaseQuery $query 查询对象
* @param string $method 查询方法
*
* @return string
*/
protected function getCacheKey(BaseQuery $query, string $method = ''): string
{
if (!empty($query->getOptions('key')) && empty($method)) {
$key = 'think_' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key');
} else {
$key = $query->getQueryGuid();
}
return $key;
}
/**
* 分析缓存.
*
* @param BaseQuery $query 查询对象
* @param array $cache 缓存信息
* @param string $method 查询方法
*
* @return CacheItem
*/
protected function parseCache(BaseQuery $query, array $cache, string $method = ''): CacheItem
{
[$key, $expire, $tag] = $cache;
if ($key instanceof CacheItem) {
$cacheItem = $key;
} else {
if (true === $key) {
$key = $this->getCacheKey($query, $method);
}
$cacheItem = new CacheItem($key);
$cacheItem->expire($expire);
$cacheItem->tag($tag);
}
return $cacheItem;
}
/**
* 获取返回或者影响的记录数.
*
* @return int
*/
public function getNumRows(): int
{
return $this->numRows;
}
/**
* 获取最终的SQL语句.
*
* @param string $sql 带参数绑定的sql语句
* @param array $bind 参数绑定列表
*
* @return string
*/
public function getRealSql(string $sql, array $bind = []): string
{
foreach ($bind as $key => $val) {
$value = strval(is_array($val) ? $val[0] : $val);
$type = is_array($val) ? $val[1] : self::PARAM_STR;
if (self::PARAM_FLOAT == $type || self::PARAM_STR == $type) {
$value = '\'' . addslashes($value) . '\'';
} elseif (self::PARAM_INT == $type && '' === $value) {
$value = '0';
}
// 判断占位符
$sql = is_numeric($key) ?
substr_replace($sql, $value, strpos($sql, '?'), 1) :
substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key));
}
return rtrim($sql);
}
/**
* 析构方法.
*/
public function __destruct()
{
// 关闭连接
$this->close();
}
}
@@ -0,0 +1,226 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db;
use Psr\SimpleCache\CacheInterface;
use think\DbManager;
/**
* Connection interface.
*/
interface ConnectionInterface
{
/**
* 获取当前连接器类对应的Query类.
*
* @return string
*/
public function getQueryClass(): string;
/**
* 指定表名开始查询.
*
* @param $table
*
* @return BaseQuery
*/
public function table($table);
/**
* 指定表名开始查询(不带前缀).
*
* @param $name
*
* @return BaseQuery
*/
public function name($name);
/**
* 连接数据库方法.
*
* @param array $config 接参数
* @param int $linkNum 连接序号
*
* @return mixed
*/
public function connect(array $config = [], $linkNum = 0);
/**
* 设置当前的数据库Db对象
*
* @param DbManager $db
*
* @return void
*/
public function setDb(DbManager $db);
/**
* 设置当前的缓存对象
*
* @param CacheInterface $cache
*
* @return void
*/
public function setCache(CacheInterface $cache);
/**
* 获取数据库的配置参数.
*
* @param string $config 配置名称
*
* @return mixed
*/
public function getConfig(string $config = '');
/**
* 关闭数据库(或者重新连接).
*
* @return $this
*/
public function close();
/**
* 查找单条记录.
*
* @param BaseQuery $query 查询对象
*
* @return array
*/
public function find(BaseQuery $query): array;
/**
* 查找记录.
*
* @param BaseQuery $query 查询对象
*
* @return array
*/
public function select(BaseQuery $query): array;
/**
* 插入记录.
*
* @param BaseQuery $query 查询对象
* @param bool $getLastInsID 返回自增主键
*
* @return mixed
*/
public function insert(BaseQuery $query, bool $getLastInsID = false);
/**
* 批量插入记录.
*
* @param BaseQuery $query 查询对象
* @param mixed $dataSet 数据集
*
* @return int
*/
public function insertAll(BaseQuery $query, array $dataSet = []): int;
/**
* 更新记录.
*
* @param BaseQuery $query 查询对象
*
* @return int
*/
public function update(BaseQuery $query): int;
/**
* 删除记录.
*
* @param BaseQuery $query 查询对象
*
* @return int
*/
public function delete(BaseQuery $query): int;
/**
* 得到某个字段的值
*
* @param BaseQuery $query 查询对象
* @param string $field 字段名
* @param mixed $default 默认值
*
* @return mixed
*/
public function value(BaseQuery $query, string $field, $default = null);
/**
* 得到某个列的数组.
*
* @param BaseQuery $query 查询对象
* @param string|array $column 字段名 多个字段用逗号分隔
* @param string $key 索引
*
* @return array
*/
public function column(BaseQuery $query, string|array $column, string $key = ''): array;
/**
* 执行数据库事务
*
* @param callable $callback 数据操作方法回调
*
* @return mixed
*/
public function transaction(callable $callback);
/**
* 启动事务
*
* @return void
*/
public function startTrans();
/**
* 用于非自动提交状态下面的查询提交.
*
* @return void
*/
public function commit();
/**
* 事务回滚.
*
* @return void
*/
public function rollback();
/**
* 取得数据表的字段信息.
*
* @param string $tableName
*
* @return array
*/
public function getTableFields(string $tableName): array;
/**
* 获取最近一次查询的sql语句.
*
* @return string
*/
public function getLastSql(): string;
/**
* 获取最近插入的ID.
*
* @param BaseQuery $query 查询对象
* @param string $sequence 自增序列名
*
* @return mixed
*/
public function getLastInsID(BaseQuery $query, string $sequence = null);
}
+514
View File
@@ -0,0 +1,514 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db;
use think\db\BaseQuery as Query;
use think\db\exception\DbException as Exception;
use think\helper\Str;
/**
* SQL获取类.
*/
class Fetch
{
/**
* Connection对象
*
* @var Connection
*/
protected $connection;
/**
* Builder对象
*
* @var Builder
*/
protected $builder;
/**
* 创建一个查询SQL获取对象
*
* @param Query $query 查询对象
*/
public function __construct(protected Query $query)
{
$this->connection = $query->getConnection();
$this->builder = $this->connection->getBuilder();
}
/**
* 聚合查询.
*
* @param string $aggregate 聚合方法
* @param string $field 字段名
*
* @return string
*/
protected function aggregate(string $aggregate, string $field): string
{
$this->query->parseOptions();
$field = $aggregate . '(' . $this->builder->parseKey($this->query, $field) . ') AS think_' . strtolower($aggregate);
return $this->value($field, 0, false);
}
/**
* 得到某个字段的值
*
* @param string $field 字段名
* @param mixed $default 默认值
* @param bool $one
*
* @return string
*/
public function value(string $field, $default = null, bool $one = true): string
{
$options = $this->query->parseOptions();
if (isset($options['field'])) {
$this->query->removeOption('field');
}
$this->query->setOption('field', (array) $field);
// 生成查询SQL
$sql = $this->builder->select($this->query, $one);
if (isset($options['field'])) {
$this->query->setOption('field', $options['field']);
} else {
$this->query->removeOption('field');
}
return $this->fetch($sql);
}
/**
* 得到某个列的数组.
*
* @param string $field 字段名 多个字段用逗号分隔
* @param string $key 索引
*
* @return string
*/
public function column(string $field, string $key = ''): string
{
$options = $this->query->parseOptions();
if (isset($options['field'])) {
$this->query->removeOption('field');
}
if ($key && '*' != $field) {
$field = $key . ',' . $field;
}
$field = array_map('trim', explode(',', $field));
$this->query->setOption('field', $field);
// 生成查询SQL
$sql = $this->builder->select($this->query);
if (isset($options['field'])) {
$this->query->setOption('field', $options['field']);
} else {
$this->query->removeOption('field');
}
return $this->fetch($sql);
}
/**
* 插入记录.
*
* @param array $data 数据
*
* @return string
*/
public function insert(array $data = []): string
{
$options = $this->query->parseOptions();
if (!empty($data)) {
$this->query->setOption('data', $data);
}
$sql = $this->builder->insert($this->query);
return $this->fetch($sql);
}
/**
* 插入记录并获取自增ID.
*
* @param array $data 数据
*
* @return string
*/
public function insertGetId(array $data = []): string
{
return $this->insert($data);
}
/**
* 保存数据 自动判断insert或者update.
*
* @param array $data 数据
* @param bool $forceInsert 是否强制insert
*
* @return string
*/
public function save(array $data = [], bool $forceInsert = false): string
{
if ($forceInsert) {
return $this->insert($data);
}
$data = array_merge($this->query->getOptions('data') ?: [], $data);
$this->query->setOption('data', $data);
if ($this->query->getOptions('where')) {
$isUpdate = true;
} else {
$isUpdate = $this->query->parseUpdateData($data);
}
return $isUpdate ? $this->update() : $this->insert();
}
/**
* 批量插入记录.
*
* @param array $dataSet 数据集
* @param int $limit 每次写入数据限制
*
* @return string
*/
public function insertAll(array $dataSet = [], int $limit = null): string
{
$options = $this->query->parseOptions();
if (empty($dataSet)) {
$dataSet = $options['data'];
}
if (empty($limit) && !empty($options['limit'])) {
$limit = $options['limit'];
}
if ($limit) {
$array = array_chunk($dataSet, $limit, true);
$fetchSql = [];
foreach ($array as $item) {
$sql = $this->builder->insertAll($this->query, $item);
$bind = $this->query->getBind();
$fetchSql[] = $this->connection->getRealSql($sql, $bind);
}
return implode(';', $fetchSql);
}
$sql = $this->builder->insertAll($this->query, $dataSet);
return $this->fetch($sql);
}
/**
* 通过Select方式插入记录.
*
* @param array $fields 要插入的数据表字段名
* @param string $table 要插入的数据表名
*
* @return string
*/
public function selectInsert(array $fields, string $table): string
{
$this->query->parseOptions();
$sql = $this->builder->selectInsert($this->query, $fields, $table);
return $this->fetch($sql);
}
/**
* 更新记录.
*
* @param mixed $data 数据
*
* @return string
*/
public function update(array $data = []): string
{
$options = $this->query->parseOptions();
$data = !empty($data) ? $data : $options['data'];
$pk = $this->query->getPk();
if (empty($options['where'])) {
// 如果存在主键数据 则自动作为更新条件
if (is_string($pk) && isset($data[$pk])) {
$this->query->where($pk, '=', $data[$pk]);
unset($data[$pk]);
} elseif (is_array($pk)) {
// 增加复合主键支持
foreach ($pk as $field) {
if (isset($data[$field])) {
$this->query->where($field, '=', $data[$field]);
} else {
// 如果缺少复合主键数据则不执行
throw new Exception('miss complex primary data');
}
unset($data[$field]);
}
}
if (empty($this->query->getOptions('where'))) {
// 如果没有任何更新条件则不执行
throw new Exception('miss update condition');
}
}
// 更新数据
$this->query->setOption('data', $data);
// 生成UPDATE SQL语句
$sql = $this->builder->update($this->query);
return $this->fetch($sql);
}
/**
* 删除记录.
*
* @param mixed $data 表达式 true 表示强制删除
*
* @return string
*/
public function delete($data = null): string
{
$options = $this->query->parseOptions();
if (!is_null($data) && true !== $data) {
// AR模式分析主键条件
$this->query->parsePkWhere($data);
}
if (!empty($options['soft_delete'])) {
// 软删除
[$field, $condition] = $options['soft_delete'];
if ($condition) {
$this->query->setOption('soft_delete', null);
$this->query->setOption('data', [$field => $condition]);
// 生成删除SQL语句
$sql = $this->builder->delete($this->query);
return $this->fetch($sql);
}
}
// 生成删除SQL语句
$sql = $this->builder->delete($this->query);
return $this->fetch($sql);
}
/**
* 查找记录 返回SQL.
*
* @param array $data
*
* @return string
*/
public function select(array $data = []): string
{
$this->query->parseOptions();
if (!empty($data)) {
// 主键条件分析
$this->query->parsePkWhere($data);
}
// 生成查询SQL
$sql = $this->builder->select($this->query);
return $this->fetch($sql);
}
/**
* 查找单条记录 返回SQL语句.
*
* @param mixed $data
*
* @return string
*/
public function find($data = null): string
{
$this->query->parseOptions();
if (!is_null($data)) {
// AR模式分析主键条件
$this->query->parsePkWhere($data);
}
// 生成查询SQL
$sql = $this->builder->select($this->query, true);
// 获取实际执行的SQL语句
return $this->fetch($sql);
}
/**
* 查找多条记录 如果不存在则抛出异常.
*
* @param mixed $data
*
* @return string
*/
public function selectOrFail($data = null): string
{
return $this->select($data);
}
/**
* 查找单条记录 如果不存在则抛出异常.
*
* @param mixed $data
*
* @return string
*/
public function findOrFail($data = null): string
{
return $this->find($data);
}
/**
* 查找单条记录 不存在返回空数据(或者空模型).
*
* @param mixed $data 数据
*
* @return string
*/
public function findOrEmpty($data = null)
{
return $this->find($data);
}
/**
* 获取实际的SQL语句.
*
* @param string $sql
*
* @return string
*/
public function fetch(string $sql): string
{
$bind = $this->query->getBind();
return $this->connection->getRealSql($sql, $bind);
}
/**
* COUNT查询.
*
* @param string $field 字段名
*
* @return string
*/
public function count(string $field = '*'): string
{
$options = $this->query->parseOptions();
if (!empty($options['group'])) {
// 支持GROUP
$subSql = $this->query->field('count(' . $field . ') AS think_count')->buildSql();
$query = $this->query->newQuery()->table([$subSql => '_group_count_']);
return $query->fetchsql()->aggregate('COUNT', '*');
} else {
return $this->aggregate('COUNT', $field);
}
}
/**
* SUM查询.
*
* @param string $field 字段名
*
* @return string
*/
public function sum(string $field): string
{
return $this->aggregate('SUM', $field);
}
/**
* MIN查询.
*
* @param string $field 字段名
*
* @return string
*/
public function min(string $field): string
{
return $this->aggregate('MIN', $field);
}
/**
* MAX查询.
*
* @param string $field 字段名
*
* @return string
*/
public function max(string $field): string
{
return $this->aggregate('MAX', $field);
}
/**
* AVG查询.
*
* @param string $field 字段名
*
* @return string
*/
public function avg(string $field): string
{
return $this->aggregate('AVG', $field);
}
public function __call($method, $args)
{
if (strtolower(substr($method, 0, 5)) == 'getby') {
// 根据某个字段获取记录
$field = Str::snake(substr($method, 5));
return $this->where($field, '=', $args[0])->find();
} elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
// 根据某个字段获取记录的某个值
$name = Str::snake(substr($method, 10));
return $this->where($name, '=', $args[0])->value($args[1]);
}
$result = call_user_func_array([$this->query, $method], $args);
return $result === $this->query ? $this : $result;
}
}
+766
View File
@@ -0,0 +1,766 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db;
use MongoDB\Driver\Command;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Exception\AuthenticationException;
use MongoDB\Driver\Exception\ConnectionException;
use MongoDB\Driver\Exception\InvalidArgumentException;
use MongoDB\Driver\Exception\RuntimeException;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use think\db\exception\DbException as Exception;
use think\Paginator;
class Mongo extends BaseQuery
{
/**
* 当前数据库连接对象
*
* @var \think\db\connector\Mongo
*/
protected $connection;
/**
* 执行指令 返回数据集.
*
* @param Command $command 指令
* @param string $dbName
* @param ReadPreference $readPreference readPreference
* @param string|array $typeMap 指定返回的typeMap
*
* @throws AuthenticationException
* @throws InvalidArgumentException
* @throws ConnectionException
* @throws RuntimeException
*
* @return mixed
*/
public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null)
{
return $this->connection->command($command, $dbName, $readPreference, $typeMap);
}
/**
* 执行command.
*
* @param string|array|object $command 指令
* @param mixed $extra 额外参数
* @param string $db 数据库名
*
* @return array
*/
public function cmd($command, $extra = null, string $db = ''): array
{
$this->parseOptions();
return $this->connection->cmd($this, $command, $extra, $db);
}
/**
* 指定distinct查询.
*
* @param string $field 字段名
*
* @return array
*/
public function getDistinct(string $field)
{
$result = $this->cmd('distinct', $field);
return $result[0]['values'];
}
/**
* 获取数据库的所有collection.
*
* @param string $db 数据库名称 留空为当前数据库
*
* @throws Exception
*/
public function listCollections(string $db = '')
{
$cursor = $this->cmd('listCollections', null, $db);
$result = [];
foreach ($cursor as $collection) {
$result[] = $collection['name'];
}
return $result;
}
/**
* COUNT查询.
*
* @param string $field 字段名
*
* @return int
*/
public function count(string $field = null): int
{
$result = $this->cmd('count');
return $result[0]['n'];
}
/**
* 聚合查询.
*
* @param string $aggregate 聚合指令
* @param string $field 字段名
* @param bool $force 强制转为数字类型
*
* @return mixed
*/
public function aggregate(string $aggregate, $field, bool $force = false, bool $one = false)
{
$result = $this->cmd('aggregate', [strtolower($aggregate), $field]);
$value = $result[0]['aggregate'] ?? 0;
if ($force) {
$value += 0;
}
return $value;
}
/**
* 多聚合操作.
*
* @param array $aggregate 聚合指令, 可以聚合多个参数, 如 ['sum' => 'field1', 'avg' => 'field2']
* @param array $groupBy 类似mysql里面的group字段, 可以传入多个字段, 如 ['field_a', 'field_b', 'field_c']
*
* @return array 查询结果
*/
public function multiAggregate(array $aggregate, array $groupBy): array
{
$result = $this->cmd('multiAggregate', [$aggregate, $groupBy]);
foreach ($result as &$row) {
if (isset($row['_id']) && !empty($row['_id'])) {
foreach ($row['_id'] as $k => $v) {
$row[$k] = $v;
}
unset($row['_id']);
}
}
return $result;
}
/**
* 字段值增长
*
* @param string $field 字段名
* @param float $step 增长值
*
* @return $this
*/
public function inc(string $field, float $step = 1)
{
$this->options['data'][$field] = ['$inc', $step];
return $this;
}
/**
* 字段值减少.
*
* @param string $field 字段名
* @param float $step 减少值
*
* @return $this
*/
public function dec(string $field, float $step = 1)
{
return $this->inc($field, -1 * $step);
}
/**
* 指定当前操作的Collection.
*
* @param string $table 表名
*
* @return $this
*/
public function table($table)
{
$this->options['table'] = $table;
return $this;
}
/**
* table方法的别名.
*
* @param string $collection
*
* @return $this
*/
public function collection(string $collection)
{
return $this->table($collection);
}
/**
* 设置typeMap.
*
* @param string|array $typeMap
*
* @return $this
*/
public function typeMap($typeMap)
{
$this->options['typeMap'] = $typeMap;
return $this;
}
/**
* awaitData.
*
* @param bool $awaitData
*
* @return $this
*/
public function awaitData(bool $awaitData)
{
$this->options['awaitData'] = $awaitData;
return $this;
}
/**
* batchSize.
*
* @param int $batchSize
*
* @return $this
*/
public function batchSize(int $batchSize)
{
$this->options['batchSize'] = $batchSize;
return $this;
}
/**
* exhaust.
*
* @param bool $exhaust
*
* @return $this
*/
public function exhaust(bool $exhaust)
{
$this->options['exhaust'] = $exhaust;
return $this;
}
/**
* 设置modifiers.
*
* @param array $modifiers
*
* @return $this
*/
public function modifiers(array $modifiers)
{
$this->options['modifiers'] = $modifiers;
return $this;
}
/**
* 设置noCursorTimeout.
*
* @param bool $noCursorTimeout
*
* @return $this
*/
public function noCursorTimeout(bool $noCursorTimeout)
{
$this->options['noCursorTimeout'] = $noCursorTimeout;
return $this;
}
/**
* 设置oplogReplay.
*
* @param bool $oplogReplay
*
* @return $this
*/
public function oplogReplay(bool $oplogReplay)
{
$this->options['oplogReplay'] = $oplogReplay;
return $this;
}
/**
* 设置partial.
*
* @param bool $partial
*
* @return $this
*/
public function partial(bool $partial)
{
$this->options['partial'] = $partial;
return $this;
}
/**
* maxTimeMS.
*
* @param string $maxTimeMS
*
* @return $this
*/
public function maxTimeMS(string $maxTimeMS)
{
$this->options['maxTimeMS'] = $maxTimeMS;
return $this;
}
/**
* collation.
*
* @param array $collation
*
* @return $this
*/
public function collation(array $collation)
{
$this->options['collation'] = $collation;
return $this;
}
/**
* 设置是否REPLACE.
*
* @param bool $replace 是否使用REPLACE写入数据
*
* @return $this
*/
public function replace(bool $replace = true)
{
return $this;
}
/**
* 设置返回字段.
*
* @param mixed $field 字段信息
*
* @return $this
*/
public function field($field)
{
if (empty($field) || '*' == $field) {
return $this;
}
if (is_string($field)) {
$field = array_map('trim', explode(',', $field));
}
$projection = [];
foreach ($field as $key => $val) {
if (is_numeric($key)) {
$projection[$val] = 1;
} else {
$projection[$key] = $val;
}
}
$this->options['projection'] = $projection;
return $this;
}
/**
* 指定要排除的查询字段.
*
* @param array|string $field 要排除的字段
*
* @return $this
*/
public function withoutField($field)
{
if (empty($field) || '*' == $field) {
return $this;
}
if (is_string($field)) {
$field = array_map('trim', explode(',', $field));
}
$projection = [];
foreach ($field as $key => $val) {
if (is_numeric($key)) {
$projection[$val] = 0;
} else {
$projection[$key] = $val;
}
}
$this->options['projection'] = $projection;
return $this;
}
/**
* 设置skip.
*
* @param int $skip
*
* @return $this
*/
public function skip(int $skip)
{
$this->options['skip'] = $skip;
return $this;
}
/**
* 设置slaveOk.
*
* @param bool $slaveOk
*
* @return $this
*/
public function slaveOk(bool $slaveOk)
{
$this->options['slaveOk'] = $slaveOk;
return $this;
}
/**
* 指定查询数量.
*
* @param int $offset 起始位置
* @param int $length 查询数量
*
* @return $this
*/
public function limit(int $offset, int $length = null)
{
if (is_null($length)) {
$length = $offset;
$offset = 0;
}
$this->options['skip'] = $offset;
$this->options['limit'] = $length;
return $this;
}
/**
* 设置sort.
*
* @param array|string $field
* @param string $order
*
* @return $this
*/
public function order($field, string $order = '')
{
if (is_array($field)) {
$this->options['sort'] = $field;
} else {
$this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1;
}
return $this;
}
/**
* 设置tailable.
*
* @param bool $tailable
*
* @return $this
*/
public function tailable(bool $tailable)
{
$this->options['tailable'] = $tailable;
return $this;
}
/**
* 设置writeConcern对象
*
* @param WriteConcern $writeConcern
*
* @return $this
*/
public function writeConcern(WriteConcern $writeConcern)
{
$this->options['writeConcern'] = $writeConcern;
return $this;
}
/**
* 获取当前数据表的主键.
*
* @return string|array
*/
public function getPk()
{
return $this->pk ?: $this->connection->getConfig('pk');
}
/**
* 执行查询但只返回Cursor对象
*
* @return Cursor
*/
public function getCursor(): Cursor
{
$this->parseOptions();
return $this->connection->getCursor($this);
}
/**
* 获取当前的查询标识.
*
* @param mixed $data 要序列化的数据
*
* @return string
*/
public function getQueryGuid($data = null): string
{
return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)));
}
/**
* 分页查询.
*
* @param int|array $listRows 每页数量 数组表示配置参数
* @param int|bool $simple 是否简洁模式或者总记录数
*
* @throws Exception
*
* @return Paginator
*/
public function paginate($listRows = null, $simple = false): Paginator
{
if (is_int($simple)) {
$total = $simple;
$simple = false;
}
$defaultConfig = [
'query' => [], //url额外参数
'fragment' => '', //url锚点
'var_page' => 'page', //分页变量
'list_rows' => 15, //每页数量
];
if (is_array($listRows)) {
$config = array_merge($defaultConfig, $listRows);
$listRows = intval($config['list_rows']);
} else {
$config = $defaultConfig;
$listRows = intval($listRows ?: $config['list_rows']);
}
$page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
$page = max($page, 1);
$config['path'] = $config['path'] ?? Paginator::getCurrentPath();
if (!isset($total) && !$simple) {
$options = $this->getOptions();
unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
$total = $this->count();
$results = $this->options($options)->page($page, $listRows)->select();
} elseif ($simple) {
$results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
$total = null;
} else {
$results = $this->page($page, $listRows)->select();
}
$this->removeOption('limit');
$this->removeOption('page');
return Paginator::make($results, $listRows, $page, $total, $simple, $config);
}
/**
* 分批数据返回处理.
*
* @param int $count 每次处理的数据数量
* @param callable $callback 处理回调方法
* @param string|array $column 分批处理的字段名
* @param string $order 字段排序
*
* @throws Exception
*
* @return bool
*/
public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
{
$options = $this->getOptions();
$column = $column ?: $this->getPk();
if (isset($options['order'])) {
unset($options['order']);
}
if (is_array($column)) {
$times = 1;
$query = $this->options($options)->page($times, $count);
} else {
$query = $this->options($options)->limit($count);
if (str_contains($column, '.')) {
[$alias, $key] = explode('.', $column);
} else {
$key = $column;
}
}
$resultSet = $query->order($column, $order)->select();
while (count($resultSet) > 0) {
if (false === call_user_func($callback, $resultSet)) {
return false;
}
if (isset($times)) {
$times++;
$query = $this->options($options)->page($times, $count);
} else {
$end = $resultSet->pop();
$lastId = is_array($end) ? $end[$key] : $end->getData($key);
$query = $this->options($options)
->limit($count)
->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
}
$resultSet = $query->order($column, $order)->select();
}
return true;
}
/**
* 分析表达式(可用于查询或者写入操作).
*
* @return array
*/
public function parseOptions(): array
{
$options = $this->options;
// 获取数据表
if (empty($options['table'])) {
$options['table'] = $this->getTable();
}
foreach (['where', 'data', 'projection', 'filter', 'json', 'with_attr', 'with_relation_attr'] as $name) {
if (!isset($options[$name])) {
$options[$name] = [];
}
}
$modifiers = empty($options['modifiers']) ? [] : $options['modifiers'];
if (isset($options['comment'])) {
$modifiers['$comment'] = $options['comment'];
}
if (isset($options['maxTimeMS'])) {
$modifiers['$maxTimeMS'] = $options['maxTimeMS'];
}
if (!empty($modifiers)) {
$options['modifiers'] = $modifiers;
}
if (!isset($options['typeMap'])) {
$options['typeMap'] = $this->getConfig('type_map');
}
if (!isset($options['limit'])) {
$options['limit'] = 0;
}
foreach (['master', 'fetch_sql', 'fetch_cursor'] as $name) {
if (!isset($options[$name])) {
$options[$name] = false;
}
}
if (isset($options['page'])) {
// 根据页数计算limit
[$page, $listRows] = $options['page'];
$page = $page > 0 ? $page : 1;
$listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
$offset = $listRows * ($page - 1);
$options['skip'] = intval($offset);
$options['limit'] = intval($listRows);
}
$this->options = $options;
return $options;
}
/**
* 获取字段类型信息.
*
* @return array
*/
public function getFieldsType(): array
{
if (!empty($this->options['field_type'])) {
return $this->options['field_type'];
}
return [];
}
/**
* 获取字段类型信息.
*
* @param string $field 字段名
*
* @return string|null
*/
public function getFieldType(string $field)
{
$fieldType = $this->getFieldsType();
return $fieldType[$field] ?? null;
}
}
File diff suppressed because it is too large Load Diff
+614
View File
@@ -0,0 +1,614 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db;
use PDOStatement;
use think\db\exception\DbException as Exception;
/**
* PDO数据查询类.
*/
class Query extends BaseQuery
{
use concern\JoinAndViewQuery;
use concern\ParamsBind;
use concern\TableFieldInfo;
/**
* 表达式方式指定Field排序.
*
* @param string $field 排序字段
* @param array $bind 参数绑定
*
* @return $this
*/
public function orderRaw(string $field, array $bind = [])
{
$this->options['order'][] = new Raw($field, $bind);
return $this;
}
/**
* 表达式方式指定查询字段.
*
* @param string $field 字段名
*
* @return $this
*/
public function fieldRaw(string $field)
{
$this->options['field'][] = new Raw($field);
return $this;
}
/**
* 指定Field排序 orderField('id',[1,2,3],'desc').
*
* @param string $field 排序字段
* @param array $values 排序值
* @param string $order 排序 desc/asc
*
* @return $this
*/
public function orderField(string $field, array $values, string $order = '')
{
if (!empty($values)) {
$values['sort'] = $order;
$this->options['order'][$field] = $values;
}
return $this;
}
/**
* 随机排序.
*
* @return $this
*/
public function orderRand()
{
$this->options['order'][] = '[rand]';
return $this;
}
/**
* 使用表达式设置数据.
*
* @param string $field 字段名
* @param string $value 字段值
*
* @return $this
*/
public function exp(string $field, string $value)
{
$this->options['data'][$field] = new Raw($value);
return $this;
}
/**
* 表达式方式指定当前操作的数据表.
*
* @param mixed $table 表名
*
* @return $this
*/
public function tableRaw(string $table)
{
$this->options['table'] = new Raw($table);
return $this;
}
/**
* 获取执行的SQL语句而不进行实际的查询.
*
* @param bool $fetch 是否返回sql
*
* @return $this|Fetch
*/
public function fetchSql(bool $fetch = true)
{
$this->options['fetch_sql'] = $fetch;
if ($fetch) {
return new Fetch($this);
}
return $this;
}
/**
* 批处理执行SQL语句
* 批处理的指令都认为是execute操作.
*
* @param array $sql SQL批处理指令
*
* @return bool
*/
public function batchQuery(array $sql = []): bool
{
return $this->connection->batchQuery($this, $sql);
}
/**
* USING支持 用于多表删除.
*
* @param mixed $using USING
*
* @return $this
*/
public function using($using)
{
$this->options['using'] = $using;
return $this;
}
/**
* 存储过程调用.
*
* @param bool $procedure 是否为存储过程查询
*
* @return $this
*/
public function procedure(bool $procedure = true)
{
$this->options['procedure'] = $procedure;
return $this;
}
/**
* 指定group查询.
*
* @param string|array $group GROUP
*
* @return $this
*/
public function group($group)
{
$this->options['group'] = $group;
return $this;
}
/**
* 指定having查询.
*
* @param string $having having
*
* @return $this
*/
public function having(string $having)
{
$this->options['having'] = $having;
return $this;
}
/**
* 指定distinct查询.
*
* @param bool $distinct 是否唯一
*
* @return $this
*/
public function distinct(bool $distinct = true)
{
$this->options['distinct'] = $distinct;
return $this;
}
/**
* 指定强制索引.
*
* @param string $force 索引名称
*
* @return $this
*/
public function force(string $force)
{
$this->options['force'] = $force;
return $this;
}
/**
* 查询注释.
*
* @param string $comment 注释
*
* @return $this
*/
public function comment(string $comment)
{
$this->options['comment'] = $comment;
return $this;
}
/**
* 设置是否REPLACE.
*
* @param bool $replace 是否使用REPLACE写入数据
*
* @return $this
*/
public function replace(bool $replace = true)
{
$this->options['replace'] = $replace;
return $this;
}
/**
* 设置当前查询所在的分区.
*
* @param string|array $partition 分区名称
*
* @return $this
*/
public function partition($partition)
{
$this->options['partition'] = $partition;
return $this;
}
/**
* 设置DUPLICATE.
*
* @param array|string|Raw $duplicate DUPLICATE信息
*
* @return $this
*/
public function duplicate($duplicate)
{
$this->options['duplicate'] = $duplicate;
return $this;
}
/**
* 设置查询的额外参数.
*
* @param string $extra 额外信息
*
* @return $this
*/
public function extra(string $extra)
{
$this->options['extra'] = $extra;
return $this;
}
/**
* 创建子查询SQL.
*
* @param bool $sub 是否添加括号
*
* @throws Exception
*
* @return string
*/
public function buildSql(bool $sub = true): string
{
return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
}
/**
* 获取当前数据表的主键.
*
* @return string|array
*/
public function getPk()
{
if (empty($this->pk)) {
$this->pk = $this->connection->getPk($this->getTable());
}
return $this->pk;
}
/**
* 指定数据表自增主键.
*
* @param string $autoinc 自增键
*
* @return $this
*/
public function autoinc(string $autoinc)
{
$this->autoinc = $autoinc;
return $this;
}
/**
* 获取当前数据表的自增主键.
*
* @return string|null
*/
public function getAutoInc()
{
$tableName = $this->getTable();
if (empty($this->autoinc) && $tableName) {
$this->autoinc = $this->connection->getAutoInc($tableName);
}
return $this->autoinc;
}
/**
* 字段值增长
*
* @param string $field 字段名
* @param float $step 增长值
*
* @return $this
*/
public function inc(string $field, float $step = 1)
{
$this->options['data'][$field] = ['INC', $step];
return $this;
}
/**
* 字段值减少.
*
* @param string $field 字段名
* @param float $step 增长值
*
* @return $this
*/
public function dec(string $field, float $step = 1)
{
$this->options['data'][$field] = ['DEC', $step];
return $this;
}
/**
* 字段值增长(支持延迟写入)
*
* @param string $field 字段名
* @param int $step 步进值
* @param int $lazyTime 延迟时间(秒)
*
* @return int|false
*/
public function setInc(string $field, int $step = 1, $lazyTime = 0)
{
if (empty($this->options['where']) && $this->model) {
$this->where($this->model->getWhere());
}
if (empty($this->options['where'])) {
// 如果没有任何更新条件则不执行
throw new Exception('miss update condition');
}
if ($lazyTime > 0) {
$guid = $this->getLazyFieldCacheKey($field);
$step = $this->lazyWrite('inc', $guid, $step, $lazyTime);
if (false === $step) {
return true;
}
}
return $this->inc($field, $step)->update();
}
/**
* 字段值减少(支持延迟写入)
*
* @param string $field 字段名
* @param int $step 步进值
* @param int $lazyTime 延迟时间(秒)
*
* @return int|false
*/
public function setDec(string $field, int $step = 1, int $lazyTime = 0)
{
if (empty($this->options['where']) && $this->model) {
$this->where($this->model->getWhere());
}
if (empty($this->options['where'])) {
// 如果没有任何更新条件则不执行
throw new Exception('miss update condition');
}
if ($lazyTime > 0) {
$guid = $this->getLazyFieldCacheKey($field);
$step = $this->lazyWrite('dec', $guid, $step, $lazyTime);
if (false === $step) {
return true;
}
return $this->inc($field, $step)->update();
}
return $this->dec($field, $step)->update();
}
/**
* 延时更新检查 返回false表示需要延时
* 否则返回实际写入的数值
* @access protected
* @param string $type 自增或者自减
* @param string $guid 写入标识
* @param int $step 写入步进值
* @param int $lazyTime 延时时间(s)
* @return false|integer
*/
protected function lazyWrite(string $type, string $guid, int $step, int $lazyTime)
{
$cache = $this->getCache();
if (!$cache->has($guid . '_time')) {
// 计时开始
$cache->set($guid . '_time', time());
$cache->$type($guid, $step);
} elseif (time() > $cache->get($guid . '_time') + $lazyTime) {
// 删除缓存
$value = $cache->$type($guid, $step);
$cache->delete($guid);
$cache->delete($guid . '_time');
return 0 === $value ? false : $value;
} else {
// 更新缓存
$cache->$type($guid, $step);
}
return false;
}
/**
* 获取延迟写入字段值.
*
* @param string $field 字段名称
* @param mixed $id 主键值
*
* @return int
*/
protected function getLazyFieldValue(string $field, $id = null): int
{
return (int) $this->getCache()->get($this->getLazyFieldCacheKey($field, $id));
}
/**
* 获取延迟写入字段的缓存Key
*
* @param string $field 字段名
* @param mixed $id 主键值
*
* @return string
*/
protected function getLazyFieldCacheKey(string $field, $id = null): string
{
return 'lazy_' . $this->getTable() . '_' . $field . '_' . ($id ?: $this->getKey());
}
/**
* 获取当前的查询标识.
*
* @param mixed $data 要序列化的数据
*
* @return string
*/
public function getQueryGuid($data = null): string
{
return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)) . serialize($this->getBind(false)));
}
/**
* 执行查询但只返回PDOStatement对象
*
* @return PDOStatement
*/
public function getPdo(): PDOStatement
{
return $this->connection->pdo($this);
}
/**
* 使用游标查找记录.
*
* @param mixed $data 数据
*
* @return \Generator
*/
public function cursor($data = null)
{
if (!is_null($data)) {
// 主键条件分析
$this->parsePkWhere($data);
}
$this->options['data'] = $data;
$connection = clone $this->connection;
return $connection->cursor($this);
}
/**
* 分批数据返回处理.
*
* @param int $count 每次处理的数据数量
* @param callable $callback 处理回调方法
* @param string|array $column 分批处理的字段名
* @param string $order 字段排序
*
* @throws Exception
*
* @return bool
*/
public function chunk(int $count, callable $callback, string|array $column = null, string $order = 'asc'): bool
{
$options = $this->getOptions();
$column = $column ?: $this->getPk();
if (isset($options['order'])) {
unset($options['order']);
}
$bind = $this->bind;
if (is_array($column)) {
$times = 1;
$query = $this->options($options)->page($times, $count);
} else {
$query = $this->options($options)->limit($count);
if (str_contains($column, '.')) {
[$alias, $key] = explode('.', $column);
} else {
$key = $column;
}
}
$resultSet = $query->order($column, $order)->select();
while (count($resultSet) > 0) {
if (false === call_user_func($callback, $resultSet)) {
return false;
}
if (isset($times)) {
$times++;
$query = $this->options($options)->page($times, $count);
} else {
$end = $resultSet->pop();
$lastId = is_array($end) ? $end[$key] : $end->getData($key);
$query = $this->options($options)
->limit($count)
->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
}
$resultSet = $query->bind($bind)->order($column, $order)->select();
}
return true;
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db;
use Stringable;
/**
* SQL Raw.
*/
class Raw
{
/**
* 创建一个查询表达式.
*
* @param string|Stringable $value
* @param array $bind
*
* @return void
*/
public function __construct(protected string|Stringable $value, protected array $bind = [])
{
}
/**
* 获取表达式.
*
* @return string
*/
public function getValue(): string
{
return $this->value;
}
/**
* 获取参数绑定.
*
* @return string
*/
public function getBind(): array
{
return $this->bind;
}
}
+175
View File
@@ -0,0 +1,175 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db;
use ArrayAccess;
/**
* 数组查询对象
*/
class Where implements ArrayAccess
{
/**
* 创建一个查询表达式.
*
* @param array $where 查询条件数组
* @param bool $enclose 是否增加括号
*/
public function __construct(protected array $where = [], protected bool $enclose = false)
{
}
/**
* 设置是否添加括号.
*
* @param bool $enclose
*
* @return $this
*/
public function enclose(bool $enclose = true)
{
$this->enclose = $enclose;
return $this;
}
/**
* 解析为Query对象可识别的查询条件数组.
*
* @return array
*/
public function parse(): array
{
$where = [];
foreach ($this->where as $key => $val) {
if ($val instanceof Raw) {
$where[] = [$key, 'exp', $val];
} elseif (is_null($val)) {
$where[] = [$key, 'NULL', ''];
} elseif (is_array($val)) {
$where[] = $this->parseItem($key, $val);
} else {
$where[] = [$key, '=', $val];
}
}
return $this->enclose ? [$where] : $where;
}
/**
* 分析查询表达式.
*
* @param string $field 查询字段
* @param array $where 查询条件
*
* @return array
*/
protected function parseItem(string $field, array $where = []): array
{
$op = $where[0];
$condition = $where[1] ?? null;
if (is_array($op)) {
// 同一字段多条件查询
array_unshift($where, $field);
} elseif (is_null($condition)) {
if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
// null查询
$where = [$field, $op, ''];
} elseif (is_null($op) || '=' == $op) {
$where = [$field, 'NULL', ''];
} elseif ('<>' == $op) {
$where = [$field, 'NOTNULL', ''];
} else {
// 字段相等查询
$where = [$field, '=', $op];
}
} else {
$where = [$field, $op, $condition];
}
return $where;
}
/**
* 修改器 设置数据对象的值
*
* @param string $name 名称
* @param mixed $value 值
*
* @return void
*/
public function __set($name, $value)
{
$this->where[$name] = $value;
}
/**
* 获取器 获取数据对象的值
*
* @param string $name 名称
*
* @return mixed
*/
public function __get($name)
{
return $this->where[$name] ?? null;
}
/**
* 检测数据对象的值
*
* @param string $name 名称
*
* @return bool
*/
public function __isset($name)
{
return isset($this->where[$name]);
}
/**
* 销毁数据对象的值
*
* @param string $name 名称
*
* @return void
*/
public function __unset($name)
{
unset($this->where[$name]);
}
// ArrayAccess
public function offsetSet(mixed $name, mixed $value): void
{
$this->__set($name, $value);
}
public function offsetExists(mixed $name): bool
{
return $this->__isset($name);
}
public function offsetUnset(mixed $name): void
{
$this->__unset($name);
}
public function offsetGet(mixed $name)
{
return $this->__get($name);
}
}
@@ -0,0 +1,692 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\builder;
use MongoDB\BSON\Javascript;
use MongoDB\BSON\ObjectID;
use MongoDB\BSON\Regex;
use MongoDB\Driver\BulkWrite;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\InvalidArgumentException;
use MongoDB\Driver\Query as MongoQuery;
use think\db\connector\Mongo as Connection;
use think\db\exception\DbException as Exception;
use think\db\Mongo as Query;
class Mongo
{
// connection对象实例
protected $connection;
// 最后插入ID
protected $insertId = [];
// 查询表达式
protected $exp = ['<>' => 'ne', '=' => 'eq', '>' => 'gt', '>=' => 'gte', '<' => 'lt', '<=' => 'lte', 'in' => 'in', 'not in' => 'nin', 'nin' => 'nin', 'mod' => 'mod', 'exists' => 'exists', 'null' => 'null', 'notnull' => 'not null', 'not null' => 'not null', 'regex' => 'regex', 'type' => 'type', 'all' => 'all', '> time' => '> time', '< time' => '< time', 'between' => 'between', 'not between' => 'not between', 'between time' => 'between time', 'not between time' => 'not between time', 'notbetween time' => 'not between time', 'like' => 'like', 'near' => 'near', 'size' => 'size'];
/**
* 架构函数.
*
* @param Connection $connection 数据库连接对象实例
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
/**
* 获取当前的连接对象实例.
*
* @return Connection
*/
public function getConnection(): Connection
{
return $this->connection;
}
/**
* key分析.
*
* @param string $key
*
* @return string
*/
protected function parseKey(Query $query, string $key): string
{
if (str_starts_with($key, '__TABLE__.')) {
[$collection, $key] = explode('.', $key, 2);
}
if ('id' == $key && $this->connection->getConfig('pk_convert_id')) {
$key = '_id';
}
return trim($key);
}
/**
* value分析.
*
* @param Query $query 查询对象
* @param mixed $value
* @param string $field
*
* @return string
*/
protected function parseValue(Query $query, $value, $field = '')
{
if ('_id' == $field && 'ObjectID' == $this->connection->getConfig('pk_type') && is_string($value)) {
try {
return new ObjectID($value);
} catch (InvalidArgumentException $e) {
return new ObjectID();
}
}
return $value;
}
/**
* insert数据分析.
*
* @param Query $query 查询对象
* @param array $data 数据
*
* @return array
*/
protected function parseData(Query $query, array $data): array
{
if (empty($data)) {
return [];
}
$result = [];
foreach ($data as $key => $val) {
$item = $this->parseKey($query, $key);
if (is_object($val)) {
$result[$item] = $val;
} elseif (isset($val[0]) && 'exp' == $val[0]) {
$result[$item] = $val[1];
} else {
$result[$item] = $this->parseValue($query, $val, $key);
}
}
return $result;
}
/**
* Set数据分析.
*
* @param Query $query 查询对象
* @param array $data 数据
*
* @return array
*/
protected function parseSet(Query $query, array $data): array
{
if (empty($data)) {
return [];
}
$result = [];
foreach ($data as $key => $val) {
$item = $this->parseKey($query, $key);
if (is_array($val) && isset($val[0]) && is_string($val[0]) && str_starts_with($val[0], '$')) {
$result[$val[0]][$item] = $this->parseValue($query, $val[1], $key);
} else {
$result['$set'][$item] = $this->parseValue($query, $val, $key);
}
}
return $result;
}
/**
* 生成查询过滤条件.
*
* @param Query $query 查询对象
* @param mixed $where
*
* @return array
*/
public function parseWhere(Query $query, array $where): array
{
if (empty($where)) {
$where = [];
}
$filter = [];
foreach ($where as $logic => $val) {
$logic = '$'.strtolower($logic);
foreach ($val as $field => $value) {
if (is_array($value)) {
if (key($value) !== 0) {
throw new Exception('where express error:'.var_export($value, true));
}
$field = array_shift($value);
} elseif (!($value instanceof \Closure)) {
throw new Exception('where express error:'.var_export($value, true));
}
if ($value instanceof \Closure) {
// 使用闭包查询
$query = new Query($this->connection);
call_user_func_array($value, [&$query]);
$filter[$logic][] = $this->parseWhere($query, $query->getOptions('where'));
} else {
if (str_contains($field, '|')) {
// 不同字段使用相同查询条件(OR)
$array = explode('|', $field);
foreach ($array as $k) {
$filter['$or'][] = $this->parseWhereItem($query, $k, $value);
}
} elseif (str_contains($field, '&')) {
// 不同字段使用相同查询条件(AND)
$array = explode('&', $field);
foreach ($array as $k) {
$filter['$and'][] = $this->parseWhereItem($query, $k, $value);
}
} else {
// 对字段使用表达式查询
$field = is_string($field) ? $field : '';
$filter[$logic][] = $this->parseWhereItem($query, $field, $value);
}
}
}
}
$options = $query->getOptions();
if (!empty($options['soft_delete'])) {
// 附加软删除条件
[$field, $condition] = $options['soft_delete'];
$filter['$and'][] = $this->parseWhereItem($query, $field, $condition);
}
return $filter;
}
// where子单元分析
protected function parseWhereItem(Query $query, $field, $val): array
{
$key = $field ? $this->parseKey($query, $field) : '';
// 查询规则和条件
if (!is_array($val)) {
$val = ['=', $val];
}
[$exp, $value] = $val;
// 对一个字段使用多个查询条件
if (is_array($exp)) {
$data = [];
foreach ($val as $value) {
$exp = $value[0];
$value = $value[1];
if (!in_array($exp, $this->exp)) {
$exp = strtolower($exp);
if (isset($this->exp[$exp])) {
$exp = $this->exp[$exp];
}
}
$k = '$'.$exp;
$data[$k] = $value;
}
$result[$key] = $data;
return $result;
} elseif (!in_array($exp, $this->exp)) {
$exp = strtolower($exp);
if (isset($this->exp[$exp])) {
$exp = $this->exp[$exp];
} else {
throw new Exception('where express error:'.$exp);
}
}
$result = [];
if ('=' == $exp) {
// 普通查询
$result[$key] = $this->parseValue($query, $value, $key);
} elseif (in_array($exp, ['neq', 'ne', 'gt', 'egt', 'gte', 'lt', 'lte', 'elt', 'mod'])) {
// 比较运算
$k = '$'.$exp;
$result[$key] = [$k => $this->parseValue($query, $value, $key)];
} elseif ('null' == $exp) {
// NULL 查询
$result[$key] = null;
} elseif ('not null' == $exp) {
$result[$key] = ['$ne' => null];
} elseif ('all' == $exp) {
// 满足所有指定条件
$result[$key] = ['$all', $this->parseValue($query, $value, $key)];
} elseif ('between' == $exp) {
// 区间查询
$value = is_array($value) ? $value : explode(',', $value);
$result[$key] = ['$gte' => $this->parseValue($query, $value[0], $key), '$lte' => $this->parseValue($query, $value[1], $key)];
} elseif ('not between' == $exp) {
// 范围查询
$value = is_array($value) ? $value : explode(',', $value);
$result[$key] = ['$lt' => $this->parseValue($query, $value[0], $key), '$gt' => $this->parseValue($query, $value[1], $key)];
} elseif ('exists' == $exp) {
// 字段是否存在
$result[$key] = ['$exists' => (bool) $value];
} elseif ('type' == $exp) {
// 类型查询
$result[$key] = ['$type' => intval($value)];
} elseif ('exp' == $exp) {
// 表达式查询
$result['$where'] = $value instanceof Javascript ? $value : new Javascript($value);
} elseif ('like' == $exp) {
// 模糊查询 采用正则方式
$result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i');
} elseif (in_array($exp, ['nin', 'in'])) {
// IN 查询
$value = is_array($value) ? $value : explode(',', $value);
foreach ($value as $k => $val) {
$value[$k] = $this->parseValue($query, $val, $key);
}
$result[$key] = ['$'.$exp => $value];
} elseif ('regex' == $exp) {
$result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i');
} elseif ('< time' == $exp) {
$result[$key] = ['$lt' => $this->parseDateTime($query, $value, $field)];
} elseif ('> time' == $exp) {
$result[$key] = ['$gt' => $this->parseDateTime($query, $value, $field)];
} elseif ('between time' == $exp) {
// 区间查询
$value = is_array($value) ? $value : explode(',', $value);
$result[$key] = ['$gte' => $this->parseDateTime($query, $value[0], $field), '$lte' => $this->parseDateTime($query, $value[1], $field)];
} elseif ('not between time' == $exp) {
// 范围查询
$value = is_array($value) ? $value : explode(',', $value);
$result[$key] = ['$lt' => $this->parseDateTime($query, $value[0], $field), '$gt' => $this->parseDateTime($query, $value[1], $field)];
} elseif ('near' == $exp) {
// 经纬度查询
$result[$key] = ['$near' => $this->parseValue($query, $value, $key)];
} elseif ('size' == $exp) {
// 元素长度查询
$result[$key] = ['$size' => intval($value)];
} else {
// 普通查询
$result[$key] = $this->parseValue($query, $value, $key);
}
return $result;
}
/**
* 日期时间条件解析.
*
* @param Query $query 查询对象
* @param string $value
* @param string $key
*
* @return string
*/
protected function parseDateTime(Query $query, $value, $key)
{
// 获取时间字段类型
$type = $query->getFieldType($key);
if ($type) {
if (is_string($value)) {
$value = strtotime($value) ?: $value;
}
if (is_int($value)) {
if (preg_match('/(datetime|timestamp)/is', $type)) {
// 日期及时间戳类型
$value = date('Y-m-d H:i:s', $value);
} elseif (preg_match('/(date)/is', $type)) {
// 日期及时间戳类型
$value = date('Y-m-d', $value);
}
}
}
return $value;
}
/**
* 获取最后写入的ID 如果是insertAll方法的话 返回所有写入的ID.
*
* @return mixed
*/
public function getLastInsID()
{
return $this->insertId;
}
/**
* 生成insert BulkWrite对象
*
* @param Query $query 查询对象
*
* @return BulkWrite
*/
public function insert(Query $query): BulkWrite
{
// 分析并处理数据
$options = $query->getOptions();
$data = $this->parseData($query, $options['data']);
$bulk = new BulkWrite();
if ($insertId = $bulk->insert($data)) {
$this->insertId = $insertId;
}
$this->log('insert', $data, $options);
return $bulk;
}
/**
* 生成insertall BulkWrite对象
*
* @param Query $query 查询对象
* @param array $dataSet 数据集
*
* @return BulkWrite
*/
public function insertAll(Query $query, array $dataSet): BulkWrite
{
$bulk = new BulkWrite();
$options = $query->getOptions();
$this->insertId = [];
foreach ($dataSet as $data) {
// 分析并处理数据
$data = $this->parseData($query, $data);
if ($insertId = $bulk->insert($data)) {
$this->insertId[] = $insertId;
}
}
$this->log('insert', $dataSet, $options);
return $bulk;
}
/**
* 生成update BulkWrite对象
*
* @param Query $query 查询对象
*
* @return BulkWrite
*/
public function update(Query $query): BulkWrite
{
$options = $query->getOptions();
$data = $this->parseSet($query, $options['data']);
$where = $this->parseWhere($query, $options['where']);
if (1 == $options['limit']) {
$updateOptions = ['multi' => false];
} else {
$updateOptions = ['multi' => true];
}
$bulk = new BulkWrite();
$bulk->update($where, $data, $updateOptions);
$this->log('update', $data, $where);
return $bulk;
}
/**
* 生成delete BulkWrite对象
*
* @param Query $query 查询对象
*
* @return BulkWrite
*/
public function delete(Query $query): BulkWrite
{
$options = $query->getOptions();
$where = $this->parseWhere($query, $options['where']);
$bulk = new BulkWrite();
if (1 == $options['limit']) {
$deleteOptions = ['limit' => 1];
} else {
$deleteOptions = ['limit' => 0];
}
$bulk->delete($where, $deleteOptions);
$this->log('remove', $where, $deleteOptions);
return $bulk;
}
/**
* 生成Mongo查询对象
*
* @param Query $query 查询对象
* @param bool $one 是否仅获取一个记录
*
* @return MongoQuery
*/
public function select(Query $query, bool $one = false): MongoQuery
{
$options = $query->getOptions();
$where = $this->parseWhere($query, $options['where']);
if ($one) {
$options['limit'] = 1;
}
$query = new MongoQuery($where, $options);
$this->log('find', $where, $options);
return $query;
}
/**
* 生成Count命令.
*
* @param Query $query 查询对象
*
* @return Command
*/
public function count(Query $query): Command
{
$options = $query->getOptions();
$cmd['count'] = $options['table'];
$cmd['query'] = (object) $this->parseWhere($query, $options['where']);
foreach (['hint', 'limit', 'maxTimeMS', 'skip'] as $option) {
if (isset($options[$option])) {
$cmd[$option] = $options[$option];
}
}
$command = new Command($cmd);
$this->log('cmd', 'count', $cmd);
return $command;
}
/**
* 聚合查询命令.
*
* @param Query $query 查询对象
* @param array $extra 指令和字段
*
* @return Command
*/
public function aggregate(Query $query, array $extra): Command
{
$options = $query->getOptions();
[$fun, $field] = $extra;
if ('id' == $field && $this->connection->getConfig('pk_convert_id')) {
$field = '_id';
}
$group = isset($options['group']) ? '$'.$options['group'] : null;
$pipeline = [
['$match' => (object) $this->parseWhere($query, $options['where'])],
['$group' => ['_id' => $group, 'aggregate' => ['$'.$fun => '$'.$field]]],
];
$cmd = [
'aggregate' => $options['table'],
'allowDiskUse' => true,
'pipeline' => $pipeline,
'cursor' => new \stdClass(),
];
foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) {
if (isset($options[$option])) {
$cmd[$option] = $options[$option];
}
}
$command = new Command($cmd);
$this->log('aggregate', $cmd);
return $command;
}
/**
* 多聚合查询命令, 可以对多个字段进行 group by 操作.
*
* @param Query $query 查询对象
* @param array $extra 指令和字段
*
* @return Command
*/
public function multiAggregate(Query $query, $extra): Command
{
$options = $query->getOptions();
[$aggregate, $groupBy] = $extra;
$groups = ['_id' => []];
foreach ($groupBy as $field) {
$groups['_id'][$field] = '$'.$field;
}
foreach ($aggregate as $fun => $field) {
$groups[$field.'_'.$fun] = ['$'.$fun => '$'.$field];
}
$pipeline = [
['$match' => (object) $this->parseWhere($query, $options['where'])],
['$group' => $groups],
];
$cmd = [
'aggregate' => $options['table'],
'allowDiskUse' => true,
'pipeline' => $pipeline,
'cursor' => new \stdClass(),
];
foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) {
if (isset($options[$option])) {
$cmd[$option] = $options[$option];
}
}
$command = new Command($cmd);
$this->log('group', $cmd);
return $command;
}
/**
* 生成distinct命令.
*
* @param Query $query 查询对象
* @param string $field 字段名
*
* @return Command
*/
public function distinct(Query $query, $field): Command
{
$options = $query->getOptions();
$cmd = [
'distinct' => $options['table'],
'key' => $field,
];
if (!empty($options['where'])) {
$cmd['query'] = (object) $this->parseWhere($query, $options['where']);
}
if (isset($options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $options['maxTimeMS'];
}
$command = new Command($cmd);
$this->log('cmd', 'distinct', $cmd);
return $command;
}
/**
* 查询所有的collection.
*
* @return Command
*/
public function listcollections(): Command
{
$cmd = ['listCollections' => 1];
$command = new Command($cmd);
$this->log('cmd', 'listCollections', $cmd);
return $command;
}
/**
* 查询数据表的状态信息.
*
* @param Query $query 查询对象
*
* @return Command
*/
public function collStats(Query $query): Command
{
$options = $query->getOptions();
$cmd = ['collStats' => $options['table']];
$command = new Command($cmd);
$this->log('cmd', 'collStats', $cmd);
return $command;
}
protected function log($type, $data, $options = [])
{
$this->connection->mongoLog($type, $data, $options);
}
}
@@ -0,0 +1,519 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\builder;
use PDO;
use think\db\Builder;
use think\db\exception\DbException as Exception;
use think\db\BaseQuery as Query;
use think\db\Raw;
/**
* mysql数据库驱动.
*/
class Mysql extends Builder
{
/**
* 查询表达式解析.
*
* @var array
*/
protected $parser = [
'parseCompare' => ['=', '<>', '>', '>=', '<', '<='],
'parseLike' => ['LIKE', 'NOT LIKE'],
'parseBetween' => ['NOT BETWEEN', 'BETWEEN'],
'parseIn' => ['NOT IN', 'IN'],
'parseExp' => ['EXP'],
'parseRegexp' => ['REGEXP', 'NOT REGEXP'],
'parseNull' => ['NOT NULL', 'NULL'],
'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'],
'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'],
'parseExists' => ['NOT EXISTS', 'EXISTS'],
'parseColumn' => ['COLUMN'],
'parseFindInSet' => ['FIND IN SET'],
];
/**
* SELECT SQL表达式.
*
* @var string
*/
protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%PARTITION%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%';
/**
* INSERT SQL表达式.
*
* @var string
*/
protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% SET %SET% %DUPLICATE%%COMMENT%';
/**
* INSERT ALL SQL表达式.
*
* @var string
*/
protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% (%FIELD%) VALUES %DATA% %DUPLICATE%%COMMENT%';
/**
* UPDATE SQL表达式.
*
* @var string
*/
protected $updateSql = 'UPDATE%EXTRA% %TABLE%%PARTITION% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%';
/**
* DELETE SQL表达式.
*
* @var string
*/
protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%PARTITION%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
/**
* 生成查询SQL.
*
* @param Query $query 查询对象
* @param bool $one 是否仅获取一个记录
*
* @return string
*/
public function select(Query $query, bool $one = false): string
{
$options = $query->getOptions();
return str_replace(
['%TABLE%', '%PARTITION%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
[
$this->parseTable($query, $options['table']),
$this->parsePartition($query, $options['partition']),
$this->parseDistinct($query, $options['distinct']),
$this->parseExtra($query, $options['extra']),
$this->parseField($query, $options['field'] ?? []),
$this->parseJoin($query, $options['join']),
$this->parseWhere($query, $options['where']),
$this->parseGroup($query, $options['group']),
$this->parseHaving($query, $options['having']),
$this->parseOrder($query, $options['order']),
$this->parseLimit($query, $one ? '1' : $options['limit']),
$this->parseUnion($query, $options['union']),
$this->parseLock($query, $options['lock']),
$this->parseComment($query, $options['comment']),
$this->parseForce($query, $options['force']),
],
$this->selectSql
);
}
/**
* 生成Insert SQL.
*
* @param Query $query 查询对象
*
* @return string
*/
public function insert(Query $query): string
{
$options = $query->getOptions();
// 分析并处理数据
$data = $this->parseData($query, $options['data']);
if (empty($data)) {
return '';
}
$set = [];
foreach ($data as $key => $val) {
$set[] = $key . ' = ' . $val;
}
return str_replace(
['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%SET%', '%DUPLICATE%', '%COMMENT%'],
[
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
$this->parseExtra($query, $options['extra']),
$this->parseTable($query, $options['table']),
$this->parsePartition($query, $options['partition']),
implode(' , ', $set),
$this->parseDuplicate($query, $options['duplicate']),
$this->parseComment($query, $options['comment']),
],
$this->insertSql
);
}
/**
* 生成insertall SQL.
*
* @param Query $query 查询对象
* @param array $dataSet 数据集
*
* @return string
*/
public function insertAll(Query $query, array $dataSet): string
{
$options = $query->getOptions();
$bind = $query->getFieldsBindType();
// 获取合法的字段
if (empty($options['field']) || '*' == $options['field']) {
$allowFields = array_keys($bind);
} else {
$allowFields = $options['field'];
}
$fields = [];
$values = [];
foreach ($dataSet as $data) {
$data = $this->parseData($query, $data, $allowFields, $bind);
$values[] = '( ' . implode(',', array_values($data)) . ' )';
if (!isset($insertFields)) {
$insertFields = array_keys($data);
}
}
foreach ($insertFields as $field) {
$fields[] = $this->parseKey($query, $field);
}
return str_replace(
['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'],
[
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
$this->parseExtra($query, $options['extra']),
$this->parseTable($query, $options['table']),
$this->parsePartition($query, $options['partition']),
implode(' , ', $fields),
implode(' , ', $values),
$this->parseDuplicate($query, $options['duplicate']),
$this->parseComment($query, $options['comment']),
],
$this->insertAllSql
);
}
/**
* 生成insertall SQL
* @access public
* @param Query $query 查询对象
* @param array $keys 键值
* @param array $values 数据
* @return string
*/
public function insertAllByKeys(Query $query, array $keys, array $datas): string
{
$options = $query->getOptions();
$bind = $query->getFieldsBindType();
$fields = [];
$values = [];
foreach ($keys as $field) {
$fields[] = $this->parseKey($query, $field);
}
foreach ($datas as $data) {
foreach ($data as $key => &$val) {
if (!$query->isAutoBind()) {
$val = PDO::PARAM_STR == $bind[$keys[$key]] ? '\'' . $val . '\'' : $val;
} else {
$val = $this->parseDataBind($query, $keys[$key], $val, $bind);
}
}
$values[] = '( ' . implode(',', $data) . ' )';
}
return str_replace(
['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'],
[
!empty($options['replace']) ? 'REPLACE' : 'INSERT',
$this->parseExtra($query, $options['extra']),
$this->parseTable($query, $options['table']),
$this->parsePartition($query, $options['partition']),
implode(' , ', $fields),
implode(' , ', $values),
$this->parseDuplicate($query, $options['duplicate']),
$this->parseComment($query, $options['comment']),
],
$this->insertAllSql
);
}
/**
* 生成update SQL.
*
* @param Query $query 查询对象
*
* @return string
*/
public function update(Query $query): string
{
$options = $query->getOptions();
$data = $this->parseData($query, $options['data']);
if (empty($data)) {
return '';
}
$set = [];
foreach ($data as $key => $val) {
$set[] = (str_contains($key, '->') ? strstr($key, '->', true) : $key) . ' = ' . $val;
}
return str_replace(
['%TABLE%', '%PARTITION%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
[
$this->parseTable($query, $options['table']),
$this->parsePartition($query, $options['partition']),
$this->parseExtra($query, $options['extra']),
implode(' , ', $set),
$this->parseJoin($query, $options['join']),
$this->parseWhere($query, $options['where']),
$this->parseOrder($query, $options['order']),
$this->parseLimit($query, $options['limit']),
$this->parseLock($query, $options['lock']),
$this->parseComment($query, $options['comment']),
],
$this->updateSql
);
}
/**
* 生成delete SQL.
*
* @param Query $query 查询对象
*
* @return string
*/
public function delete(Query $query): string
{
$options = $query->getOptions();
return str_replace(
['%TABLE%', '%PARTITION%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
[
$this->parseTable($query, $options['table']),
$this->parsePartition($query, $options['partition']),
$this->parseExtra($query, $options['extra']),
!empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '',
$this->parseJoin($query, $options['join']),
$this->parseWhere($query, $options['where']),
$this->parseOrder($query, $options['order']),
$this->parseLimit($query, $options['limit']),
$this->parseLock($query, $options['lock']),
$this->parseComment($query, $options['comment']),
],
$this->deleteSql
);
}
/**
* 正则查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
*
* @return string
*/
protected function parseRegexp(Query $query, string $key, string $exp, $value, string $field): string
{
if ($value instanceof Raw) {
$value = $this->parseRaw($query, $value);
}
return $key . ' ' . $exp . ' ' . $value;
}
/**
* FIND_IN_SET 查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
*
* @return string
*/
protected function parseFindInSet(Query $query, string $key, string $exp, $value, string $field): string
{
if ($value instanceof Raw) {
$value = $this->parseRaw($query, $value);
}
return 'FIND_IN_SET(' . $value . ', ' . $key . ')';
}
/**
* 字段和表名处理.
*
* @param Query $query 查询对象
* @param mixed $key 字段名
* @param bool $strict 严格检测
*
* @return string
*/
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
{
if (is_int($key)) {
return (string) $key;
} elseif ($key instanceof Raw) {
return $this->parseRaw($query, $key);
}
$key = trim($key);
if (str_contains($key, '->>') && !str_contains($key, '(')) {
// JSON字段支持
[$field, $name] = explode('->>', $key, 2);
return $this->parseKey($query, $field, true) . '->>\'$' . (str_starts_with($name, '[') ? '' : '.') . str_replace('->>', '.', $name) . '\'';
} elseif (str_contains($key, '->') && !str_contains($key, '(')) {
// JSON字段支持
[$field, $name] = explode('->', $key, 2);
return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (str_starts_with($name, '[') ? '' : '.') . str_replace('->', '.', $name) . '\')';
} elseif (str_contains($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
[$table, $key] = explode('.', $key, 2);
$alias = $query->getOptions('alias');
if ('__TABLE__' == $table) {
$table = $query->getOptions('table');
$table = is_array($table) ? array_shift($table) : $table;
}
if (isset($alias[$table])) {
$table = $alias[$table];
}
}
if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
throw new Exception('not support data:' . $key);
}
if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
$key = '`' . $key . '`';
}
if (isset($table)) {
if (str_contains($table, '.')) {
$table = str_replace('.', '`.`', $table);
}
$key = '`' . $table . '`.' . $key;
}
return $key;
}
/**
* Null查询.
*
* @param Query $query 查询对象
* @param string $key
* @param string $exp
* @param mixed $value
* @param string $field
* @param int $bindType
*
* @return string
*/
protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string
{
if (str_starts_with($key, "json_extract")) {
if ($exp === 'NULL') {
return '(' . $key . ' is null OR json_type(' . $key . ') = \'NULL\')';
}elseif ($exp === 'NOT NULL'){
return '(' . $key . ' is not null AND json_type(' . $key . ') != \'NULL\')';
}
}
return parent::parseNull($query, $key, $exp, $value, $field, $bindType);
}
/**
* 随机排序.
*
* @param Query $query 查询对象
*
* @return string
*/
protected function parseRand(Query $query): string
{
return 'rand()';
}
/**
* Partition 分析.
*
* @param Query $query 查询对象
* @param string|array $partition 分区
*
* @return string
*/
protected function parsePartition(Query $query, $partition): string
{
if ('' == $partition) {
return '';
}
if (is_string($partition)) {
$partition = explode(',', $partition);
}
return ' PARTITION (' . implode(' , ', $partition) . ') ';
}
/**
* ON DUPLICATE KEY UPDATE 分析.
*
* @param Query $query 查询对象
* @param mixed $duplicate
*
* @return string
*/
protected function parseDuplicate(Query $query, $duplicate): string
{
if ('' == $duplicate) {
return '';
}
if ($duplicate instanceof Raw) {
return ' ON DUPLICATE KEY UPDATE ' . $this->parseRaw($query, $duplicate) . ' ';
}
if (is_string($duplicate)) {
$duplicate = explode(',', $duplicate);
}
$updates = [];
foreach ($duplicate as $key => $val) {
if (is_numeric($key)) {
$val = $this->parseKey($query, $val);
$updates[] = $val . ' = VALUES(' . $val . ')';
} elseif ($val instanceof Raw) {
$updates[] = $this->parseKey($query, $key) . ' = ' . $this->parseRaw($query, $val);
} else {
$name = $query->bindValue($val, $query->getConnection()->getFieldBindType($key));
$updates[] = $this->parseKey($query, $key) . ' = :' . $name;
}
}
return ' ON DUPLICATE KEY UPDATE ' . implode(' , ', $updates) . ' ';
}
}
@@ -0,0 +1,134 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\builder;
use think\db\Builder;
use think\db\exception\DbException as Exception;
use think\db\BaseQuery as Query;
use think\db\Raw;
/**
* Oracle数据库驱动.
*/
class Oracle extends Builder
{
protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%';
/**
* limit分析.
*
* @param Query $query 查询对象
* @param mixed $limit
*
* @return string
*/
protected function parseLimit(Query $query, string $limit): string
{
$limitStr = '';
if (!empty($limit)) {
$limit = explode(',', $limit);
if (count($limit) > 1) {
$limitStr = '(numrow>' . $limit[0] . ') AND (numrow<=' . ($limit[0] + $limit[1]) . ')';
} else {
$limitStr = '(numrow>0 AND numrow<=' . $limit[0] . ')';
}
}
return $limitStr ? ' WHERE ' . $limitStr : '';
}
/**
* 设置锁机制.
*
* @param Query $query 查询对象
* @param bool|string $lock
*
* @return string
*/
protected function parseLock(Query $query, bool|string $lock = false): string
{
if (!$lock) {
return '';
}
return ' FOR UPDATE NOWAIT ';
}
/**
* 字段和表名处理.
*
* @param Query $query 查询对象
* @param string|int|Raw $key
* @param bool $strict
*
* @throws Exception
*
* @return string
*/
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
{
if (is_int($key)) {
return (string) $key;
} elseif ($key instanceof Raw) {
return $this->parseRaw($query, $key);
}
$key = trim($key);
if (str_contains($key, '->') && !str_contains($key, '(')) {
// JSON字段支持
[$field, $name] = explode($key, '->');
$key = $field . '."' . $name . '"';
} elseif (str_contains($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) {
[$table, $key] = explode('.', $key, 2);
$alias = $query->getOptions('alias');
if ('__TABLE__' == $table) {
$table = $query->getOptions('table');
$table = is_array($table) ? array_shift($table) : $table;
}
if (isset($alias[$table])) {
$table = $alias[$table];
}
}
if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
throw new Exception('not support data:' . $key);
}
if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) {
$key = '"' . $key . '"';
}
if (isset($table)) {
$key = '"' . $table . '".' . $key;
}
return $key;
}
/**
* 随机排序.
*
* @param Query $query 查询对象
*
* @return string
*/
protected function parseRand(Query $query): string
{
return 'DBMS_RANDOM.value';
}
}
@@ -0,0 +1,123 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\builder;
use think\db\Builder;
use think\db\BaseQuery as Query;
use think\db\Raw;
/**
* Pgsql数据库驱动.
*/
class Pgsql extends Builder
{
/**
* INSERT SQL表达式.
*
* @var string
*/
protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
/**
* INSERT ALL SQL表达式.
*
* @var string
*/
protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
/**
* limit分析.
*
* @param Query $query 查询对象
* @param mixed $limit
*
* @return string
*/
public function parseLimit(Query $query, string $limit): string
{
$limitStr = '';
if (!empty($limit)) {
$limit = explode(',', $limit);
if (count($limit) > 1) {
$limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' ';
} else {
$limitStr .= ' LIMIT ' . $limit[0] . ' ';
}
}
return $limitStr;
}
/**
* 字段和表名处理.
*
* @param Query $query 查询对象
* @param string|int|Raw $key 字段名
* @param bool $strict 严格检测
*
* @return string
*/
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
{
if (is_int($key)) {
return (string) $key;
} elseif ($key instanceof Raw) {
return $this->parseRaw($query, $key);
}
$key = trim($key);
if (str_contains($key, '->') && !str_contains($key, '(')) {
// JSON字段支持
[$field, $name] = explode('->', $key);
$key = '"' . $field . '"' . '->>\'' . $name . '\'';
} elseif (str_contains($key, '.')) {
[$table, $key] = explode('.', $key, 2);
$alias = $query->getOptions('alias');
if ('__TABLE__' == $table) {
$table = $query->getOptions('table');
$table = is_array($table) ? array_shift($table) : $table;
}
if (isset($alias[$table])) {
$table = $alias[$table];
}
if ('*' != $key && !preg_match('/[,\"\*\(\).\s]/', $key)) {
$key = '"' . $key . '"';
}
}
if (isset($table)) {
$key = $table . '.' . $key;
}
return $key;
}
/**
* 随机排序.
*
* @param Query $query 查询对象
*
* @return string
*/
protected function parseRand(Query $query): string
{
return 'RANDOM()';
}
}
@@ -0,0 +1,118 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\builder;
use think\db\Builder;
use think\db\BaseQuery as Query;
use think\db\Raw;
/**
* Sqlite数据库驱动.
*/
class Sqlite extends Builder
{
/**
* limit.
*
* @param Query $query 查询对象
* @param mixed $limit
*
* @return string
*/
public function parseLimit(Query $query, string $limit): string
{
$limitStr = '';
if (!empty($limit)) {
$limit = explode(',', $limit);
if (count($limit) > 1) {
$limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' ';
} else {
$limitStr .= ' LIMIT ' . $limit[0] . ' ';
}
}
return $limitStr;
}
/**
* 随机排序.
*
* @param Query $query 查询对象
*
* @return string
*/
protected function parseRand(Query $query): string
{
return 'RANDOM()';
}
/**
* 字段和表名处理.
*
* @param Query $query 查询对象
* @param string|int|Raw $key 字段名
* @param bool $strict 严格检测
*
* @return string
*/
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
{
if (is_int($key)) {
return (string) $key;
} elseif ($key instanceof Raw) {
return $this->parseRaw($query, $key);
}
$key = trim($key);
if (str_contains($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
[$table, $key] = explode('.', $key, 2);
$alias = $query->getOptions('alias');
if ('__TABLE__' == $table) {
$table = $query->getOptions('table');
$table = is_array($table) ? array_shift($table) : $table;
}
if (isset($alias[$table])) {
$table = $alias[$table];
}
}
if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
$key = '`' . $key . '`';
}
if (isset($table)) {
$key = '`' . $table . '`.' . $key;
}
return $key;
}
/**
* 设置锁机制.
*
* @param Query $query 查询对象
* @param bool|string $lock
*
* @return string
*/
protected function parseLock(Query $query, bool|string $lock = false): string
{
return '';
}
}
@@ -0,0 +1,194 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\db\builder;
use think\db\Builder;
use think\db\exception\DbException as Exception;
use think\db\BaseQuery as Query;
use think\db\Raw;
/**
* Sqlsrv数据库驱动.
*/
class Sqlsrv extends Builder
{
/**
* SELECT SQL表达式.
*
* @var string
*/
protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%';
/**
* SELECT INSERT SQL表达式.
*
* @var string
*/
protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%';
/**
* UPDATE SQL表达式.
*
* @var string
*/
protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
/**
* DELETE SQL表达式.
*
* @var string
*/
protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
/**
* INSERT SQL表达式.
*
* @var string
*/
protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
/**
* INSERT ALL SQL表达式.
*
* @var string
*/
protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
/**
* order分析.
*
* @param Query $query 查询对象
* @param mixed $order
*
* @return string
*/
protected function parseOrder(Query $query, array $order): string
{
if (empty($order)) {
return ' ORDER BY rand()';
}
$array = [];
foreach ($order as $key => $val) {
if ($val instanceof Raw) {
$array[] = $this->parseRaw($query, $val);
} elseif ('[rand]' == $val) {
$array[] = $this->parseRand($query);
} else {
if (is_numeric($key)) {
[$key, $sort] = explode(' ', str_contains($val, ' ') ? $val : $val . ' ');
} else {
$sort = $val;
}
$sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : '';
$array[] = $this->parseKey($query, $key, true) . $sort;
}
}
return ' ORDER BY ' . implode(',', $array);
}
/**
* 随机排序.
*
* @param Query $query 查询对象
*
* @return string
*/
protected function parseRand(Query $query): string
{
return 'rand()';
}
/**
* 字段和表名处理.
*
* @param Query $query 查询对象
* @param string|int|Raw $key 字段名
* @param bool $strict 严格检测
*
* @return string
*/
public function parseKey(Query $query, string|int|Raw $key, bool $strict = false): string
{
if (is_int($key)) {
return (string) $key;
} elseif ($key instanceof Raw) {
return $this->parseRaw($query, $key);
}
$key = trim($key);
if (str_contains($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) {
[$table, $key] = explode('.', $key, 2);
$alias = $query->getOptions('alias');
if ('__TABLE__' == $table) {
$table = $query->getOptions('table');
$table = is_array($table) ? array_shift($table) : $table;
}
if (isset($alias[$table])) {
$table = $alias[$table];
}
}
if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
throw new Exception('not support data:' . $key);
}
if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) {
$key = '[' . $key . ']';
}
if (isset($table)) {
$key = '[' . $table . '].' . $key;
}
return $key;
}
/**
* limit.
*
* @param Query $query 查询对象
* @param mixed $limit
*
* @return string
*/
protected function parseLimit(Query $query, string $limit): string
{
if (empty($limit)) {
return '';
}
$limit = explode(',', $limit);
if (count($limit) > 1) {
$limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')';
} else {
$limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ')';
}
return 'WHERE ' . $limitStr;
}
public function selectInsert(Query $query, array $fields, string $table): string
{
$this->selectSql = $this->selectInsertSql;
return parent::selectInsert($query, $fields, $table);
}
}
@@ -0,0 +1,128 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\concern;
use think\db\exception\DbException;
use think\db\Raw;
/**
* 聚合查询.
*/
trait AggregateQuery
{
/**
* 聚合查询.
*
* @param string $aggregate 聚合方法
* @param string|Raw $field 字段名
* @param bool $force 强制转为数字类型
*
* @return mixed
*/
protected function aggregate(string $aggregate, string|Raw $field, bool $force = false, bool $one = false)
{
return $this->connection->aggregate($this, $aggregate, $field, $force, $one);
}
/**
* COUNT查询.
*
* @param string $field 字段名
*
* @return int
*/
public function count(string $field = '*'): int
{
if (!empty($this->options['group'])) {
// 支持GROUP
if (!preg_match('/^[\w\.\*]+$/', $field)) {
throw new DbException('not support data:' . $field);
}
$options = $this->getOptions();
if (isset($options['cache'])) {
$cache = $options['cache'];
unset($options['cache']);
}
$subSql = $this->options($options)
->field('count(' . $field . ') AS think_count')
->bind($this->bind)
->buildSql();
$query = $this->newQuery();
if (isset($cache)) {
$query->setOption('cache', $cache);
}
$query->table([$subSql => '_group_count_']);
$count = $query->aggregate('COUNT', '*');
} else {
$count = $this->aggregate('COUNT', $field);
}
return (int) $count;
}
/**
* SUM查询.
*
* @param string|Raw $field 字段名
*
* @return float
*/
public function sum(string|Raw $field): float
{
return $this->aggregate('SUM', $field, true);
}
/**
* MIN查询.
*
* @param string|Raw $field 字段名
* @param bool $force 强制转为数字类型
*
* @return mixed
*/
public function min(string|Raw $field, bool $force = true)
{
return $this->aggregate('MIN', $field, $force);
}
/**
* MAX查询.
*
* @param string|Raw $field 字段名
* @param bool $force 强制转为数字类型
*
* @return mixed
*/
public function max(string|Raw $field, bool $force = true)
{
return $this->aggregate('MAX', $field, $force);
}
/**
* AVG查询.
*
* @param string|Raw $field 字段名
*
* @return float
*/
public function avg(string|Raw $field): float
{
return $this->aggregate('AVG', $field, true);
}
}
@@ -0,0 +1,235 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\concern;
use think\db\Raw;
/**
* JOIN和VIEW查询.
*/
trait JoinAndViewQuery
{
/**
* 查询SQL组装 join.
*
* @param array|string|Raw $join 关联的表名
* @param mixed $condition 条件
* @param string $type JOIN类型
* @param array $bind 参数绑定
*
* @return $this
*/
public function join(array|string|Raw $join, string $condition = null, string $type = 'INNER', array $bind = [])
{
$table = $this->getJoinTable($join);
if (!empty($bind) && $condition) {
$this->bindParams($condition, $bind);
}
$this->options['join'][] = [$table, strtoupper($type), $condition];
return $this;
}
/**
* LEFT JOIN.
*
* @param array|string|Raw $join 关联的表名
* @param mixed $condition 条件
* @param array $bind 参数绑定
*
* @return $this
*/
public function leftJoin(array|string|Raw $join, string $condition = null, array $bind = [])
{
return $this->join($join, $condition, 'LEFT', $bind);
}
/**
* RIGHT JOIN.
*
* @param array|string|Raw $join 关联的表名
* @param mixed $condition 条件
* @param array $bind 参数绑定
*
* @return $this
*/
public function rightJoin(array|string|Raw $join, string $condition = null, array $bind = [])
{
return $this->join($join, $condition, 'RIGHT', $bind);
}
/**
* FULL JOIN.
*
* @param array|string|Raw $join 关联的表名
* @param mixed $condition 条件
* @param array $bind 参数绑定
*
* @return $this
*/
public function fullJoin(array|string|Raw $join, string $condition = null, array $bind = [])
{
return $this->join($join, $condition, 'FULL');
}
/**
* 获取Join表名及别名 支持
* ['prefix_table或者子查询'=>'alias'] 'table alias'.
*
* @param array|string|Raw $join JION表名
* @param string $alias 别名
*
* @return string|array
*/
protected function getJoinTable(array|string|Raw $join, string &$alias = null)
{
if (is_array($join)) {
$table = $join;
$alias = array_shift($join);
return $table;
} elseif ($join instanceof Raw) {
return $join;
}
$join = trim($join);
if (str_contains($join, '(')) {
// 使用子查询
$table = $join;
} else {
// 使用别名
if (str_contains($join, ' ')) {
// 使用别名
[$table, $alias] = explode(' ', $join);
} else {
$table = $join;
if (!str_contains($join, '.')) {
$alias = $join;
}
}
if ($this->prefix && !str_contains($table, '.') && !str_starts_with($table, $this->prefix)) {
$table = $this->getTable($table);
}
}
if (!empty($alias) && $table != $alias) {
$table = [$table => $alias];
}
return $table;
}
/**
* 指定JOIN查询字段.
*
* @param array|string|Raw $join 数据表
* @param string|array|bool $field 查询字段
* @param string $on JOIN条件
* @param string $type JOIN类型
* @param array $bind 参数绑定
*
* @return $this
*/
public function view(array|string|Raw $join, string|array|bool $field = true, string $on = null, string $type = 'INNER', array $bind = [])
{
$this->options['view'] = true;
$fields = [];
$table = $this->getJoinTable($join, $alias);
if (true === $field) {
$fields = $alias . '.*';
} else {
if (is_string($field)) {
$field = explode(',', $field);
}
foreach ($field as $key => $val) {
if (is_numeric($key)) {
$fields[] = $alias . '.' . $val;
$this->options['map'][$val] = $alias . '.' . $val;
} else {
if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
$name = $key;
} else {
$name = $alias . '.' . $key;
}
$fields[] = $name . ' AS ' . $val;
$this->options['map'][$val] = $name;
}
}
}
$this->field($fields);
if ($on) {
$this->join($table, $on, $type, $bind);
} else {
$this->table($table);
}
return $this;
}
/**
* 视图查询处理.
*
* @param array $options 查询参数
*
* @return void
*/
protected function parseView(array &$options): void
{
foreach (['AND', 'OR'] as $logic) {
if (isset($options['where'][$logic])) {
foreach ($options['where'][$logic] as $key => $val) {
if (array_key_exists($key, $options['map'])) {
array_shift($val);
array_unshift($val, $options['map'][$key]);
$options['where'][$logic][$options['map'][$key]] = $val;
unset($options['where'][$logic][$key]);
}
}
}
}
if (isset($options['order'])) {
// 视图查询排序处理
foreach ($options['order'] as $key => $val) {
if (is_numeric($key) && is_string($val)) {
if (str_contains($val, ' ')) {
[$field, $sort] = explode(' ', $val);
if (array_key_exists($field, $options['map'])) {
$options['order'][$options['map'][$field]] = $sort;
unset($options['order'][$key]);
}
} elseif (array_key_exists($val, $options['map'])) {
$options['order'][$options['map'][$val]] = 'asc';
unset($options['order'][$key]);
}
} elseif (array_key_exists($key, $options['map'])) {
$options['order'][$options['map'][$key]] = $val;
unset($options['order'][$key]);
}
}
}
}
}
@@ -0,0 +1,641 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\concern;
use Closure;
use think\helper\Str;
use think\Model;
use think\model\Collection as ModelCollection;
/**
* 模型及关联查询.
*/
trait ModelRelationQuery
{
/**
* 当前模型对象
*
* @var Model
*/
protected $model;
/**
* 指定模型.
*
* @param Model $model 模型对象实例
*
* @return $this
*/
public function model(Model $model)
{
$this->model = $model;
return $this;
}
/**
* 获取当前的模型对象
*
* @return Model|null
*/
public function getModel()
{
return $this->model;
}
/**
* 设置需要隐藏的输出属性.
*
* @param array $hidden 属性列表
*
* @return $this
*/
public function hidden(array $hidden = [])
{
$this->options['hidden'] = $hidden;
return $this;
}
/**
* 设置需要输出的属性.
*
* @param array $visible
*
* @return $this
*/
public function visible(array $visible = [])
{
$this->options['visible'] = $visible;
return $this;
}
/**
* 设置需要附加的输出属性.
*
* @param array $append 属性列表
*
* @return $this
*/
public function append(array $append = [])
{
$this->options['append'] = $append;
return $this;
}
/**
* 添加查询范围.
*
* @param array|string|Closure $scope 查询范围定义
* @param array $args 参数
*
* @return $this
*/
public function scope($scope, ...$args)
{
// 查询范围的第一个参数始终是当前查询对象
array_unshift($args, $this);
if ($scope instanceof Closure) {
call_user_func_array($scope, $args);
return $this;
}
if (is_string($scope)) {
$scope = explode(',', $scope);
}
if ($this->model) {
// 检查模型类的查询范围方法
foreach ($scope as $name) {
$method = 'scope' . trim($name);
if (method_exists($this->model, $method)) {
call_user_func_array([$this->model, $method], $args);
}
}
}
return $this;
}
/**
* 设置关联查询.
*
* @param array $relation 关联名称
*
* @return $this
*/
public function relation(array $relation)
{
if (empty($this->model) || empty($relation)) {
return $this;
}
$this->options['relation'] = $relation;
return $this;
}
/**
* 使用搜索器条件搜索字段.
*
* @param string|array $fields 搜索字段
* @param mixed $data 搜索数据
* @param string $prefix 字段前缀标识
*
* @return $this
*/
public function withSearch($fields, $data = [], string $prefix = '')
{
if (is_string($fields)) {
$fields = explode(',', $fields);
}
$likeFields = $this->getConfig('match_like_fields') ?: [];
foreach ($fields as $key => $field) {
if ($field instanceof Closure) {
$field($this, $data[$key] ?? null, $data, $prefix);
} elseif ($this->model) {
// 检测搜索器
$fieldName = is_numeric($key) ? $field : $key;
$method = 'search' . Str::studly($fieldName) . 'Attr';
if (method_exists($this->model, $method)) {
$this->model->$method($this, $data[$field] ?? null, $data, $prefix);
} elseif (isset($data[$field])) {
$this->where($fieldName, in_array($fieldName, $likeFields) ? 'like' : '=', in_array($fieldName, $likeFields) ? '%' . $data[$field] . '%' : $data[$field]);
}
}
}
return $this;
}
/**
* 限制关联数据的字段 已废弃直接使用field或withoutfield替代.
*
* @deprecated
*
* @param array|string $field 关联字段限制
*
* @return $this
*/
public function withField($field)
{
return $this->field($field);
}
/**
* 限制关联数据的数量 已废弃直接使用limit替代.
*
* @deprecated
*
* @param int $limit 关联数量限制
*
* @return $this
*/
public function withLimit(int $limit)
{
return $this->limit($limit);
}
/**
* 设置关联数据不存在的时候默认值
*
* @param mixed $data 默认值
*
* @return $this
*/
public function withDefault($data = null)
{
$this->options['default_model'] = $data;
return $this;
}
/**
* 设置数据字段获取器.
*
* @param string|array $name 字段名
* @param callable $callback 闭包获取器
*
* @return $this
*/
public function withAttr(string|array $name, callable $callback = null)
{
if (is_array($name)) {
foreach ($name as $key => $val) {
$this->withAttr($key, $val);
}
return $this;
}
$this->options['with_attr'][$name] = $callback;
if (str_contains($name, '.')) {
[$relation, $field] = explode('.', $name);
if (!empty($this->options['json']) && in_array($relation, $this->options['json'])) {
} else {
$this->options['with_relation_attr'][$relation][$field] = $callback;
unset($this->options['with_attr'][$name]);
}
}
return $this;
}
/**
* 关联预载入 In方式.
*
* @param array|string $with 关联方法名称
*
* @return $this
*/
public function with(array|string $with)
{
if (empty($this->model) || empty($with)) {
return $this;
}
$this->options['with'] = (array) $with;
return $this;
}
/**
* 关联预载入 JOIN方式.
*
* @param array|string $with 关联方法名
* @param string $joinType JOIN方式
*
* @return $this
*/
public function withJoin(array|string $with, string $joinType = '')
{
if (empty($this->model) || empty($with)) {
return $this;
}
$with = (array) $with;
$first = true;
foreach ($with as $key => $relation) {
$closure = null;
$field = true;
if ($relation instanceof Closure) {
// 支持闭包查询过滤关联条件
$closure = $relation;
$relation = $key;
} elseif (is_array($relation)) {
$field = $relation;
$relation = $key;
} elseif (is_string($relation) && str_contains($relation, '.')) {
$relation = strstr($relation, '.', true);
}
$result = $this->model->eagerly($this, $relation, $field, $joinType, $closure, $first);
if (!$result) {
unset($with[$key]);
} else {
$first = false;
}
}
$this->via();
$this->options['with_join'] = $with;
return $this;
}
/**
* 关联统计
*
* @param array|string $relations 关联方法名
* @param string $aggregate 聚合查询方法
* @param string $field 字段
* @param bool $subQuery 是否使用子查询
*
* @return $this
*/
protected function withAggregate(string|array $relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
{
if (empty($this->model)) {
return $this;
}
if (!$subQuery) {
$this->options['with_aggregate'][] = [(array) $relations, $aggregate, $field];
return $this;
}
if (!isset($this->options['field'])) {
$this->field('*');
}
$this->model->relationCount($this, (array) $relations, $aggregate, $field, true);
return $this;
}
/**
* 关联缓存.
*
* @param string|array|bool $relation 关联方法名
* @param mixed $key 缓存key
* @param int|\DateTime $expire 缓存有效期
* @param string $tag 缓存标签
*
* @return $this
*/
public function withCache(string|array|bool $relation = true, $key = true, $expire = null, string $tag = null)
{
if (empty($this->model)) {
return $this;
}
if (false === $relation || false === $key || !$this->getConnection()->getCache()) {
return $this;
}
if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) {
$expire = $key;
$key = true;
}
if (true === $relation || is_numeric($relation)) {
$this->options['with_cache'] = $relation;
return $this;
}
$relations = (array) $relation;
foreach ($relations as $name => $relation) {
if (!is_numeric($name)) {
$this->options['with_cache'][$name] = is_array($relation) ? $relation : [$key, $relation, $tag];
} else {
$this->options['with_cache'][$relation] = [$key, $expire, $tag];
}
}
return $this;
}
/**
* 关联统计
*
* @param string|array $relation 关联方法名
* @param bool $subQuery 是否使用子查询
*
* @return $this
*/
public function withCount(string|array $relation, bool $subQuery = true)
{
return $this->withAggregate($relation, 'count', '*', $subQuery);
}
/**
* 关联统计Sum.
*
* @param string|array $relation 关联方法名
* @param string $field 字段
* @param bool $subQuery 是否使用子查询
*
* @return $this
*/
public function withSum(string|array $relation, string $field, bool $subQuery = true)
{
return $this->withAggregate($relation, 'sum', $field, $subQuery);
}
/**
* 关联统计Max.
*
* @param string|array $relation 关联方法名
* @param string $field 字段
* @param bool $subQuery 是否使用子查询
*
* @return $this
*/
public function withMax(string|array $relation, string $field, bool $subQuery = true)
{
return $this->withAggregate($relation, 'max', $field, $subQuery);
}
/**
* 关联统计Min.
*
* @param string|array $relation 关联方法名
* @param string $field 字段
* @param bool $subQuery 是否使用子查询
*
* @return $this
*/
public function withMin(string|array $relation, string $field, bool $subQuery = true)
{
return $this->withAggregate($relation, 'min', $field, $subQuery);
}
/**
* 关联统计Avg.
*
* @param string|array $relation 关联方法名
* @param string $field 字段
* @param bool $subQuery 是否使用子查询
*
* @return $this
*/
public function withAvg(string|array $relation, string $field, bool $subQuery = true)
{
return $this->withAggregate($relation, 'avg', $field, $subQuery);
}
/**
* 根据关联条件查询当前模型.
*
* @param string $relation 关联方法名
* @param mixed $operator 比较操作符
* @param int $count 个数
* @param string $id 关联表的统计字段
* @param string $joinType JOIN类型
*
* @return $this
*/
public function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '')
{
return $this->model->has($relation, $operator, $count, $id, $joinType, $this);
}
/**
* 根据关联条件查询当前模型.
*
* @param string $relation 关联方法名
* @param mixed $where 查询条件(数组或者闭包)
* @param mixed $fields 字段
* @param string $joinType JOIN类型
*
* @return $this
*/
public function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '')
{
return $this->model->hasWhere($relation, $where, $fields, $joinType, $this);
}
/**
* JSON字段数据转换.
*
* @param array $result 查询数据
*
* @return void
*/
protected function jsonModelResult(array &$result): void
{
$withAttr = $this->options['with_attr'];
foreach ($this->options['json'] as $name) {
if (!isset($result[$name])) {
continue;
}
$jsonData = json_decode($result[$name], true);
if (isset($withAttr[$name])) {
foreach ($withAttr[$name] as $key => $closure) {
$jsonData[$key] = $closure($jsonData[$key] ?? null, $jsonData);
}
}
$result[$name] = !$this->options['json_assoc'] ? (object) $jsonData : $jsonData;
}
}
/**
* 查询数据转换为模型数据集对象
*
* @param array $resultSet 数据集
*
* @return ModelCollection
*/
protected function resultSetToModelCollection(array $resultSet): ModelCollection
{
if (empty($resultSet)) {
return $this->model->toCollection();
}
$this->options['is_resultSet'] = true;
foreach ($resultSet as $key => &$result) {
// 数据转换为模型对象
$this->resultToModel($result);
}
foreach (['with', 'with_join'] as $with) {
// 关联预载入
if (!empty($this->options[$with])) {
$result->eagerlyResultSet(
$resultSet,
$this->options[$with],
$this->options['with_relation_attr'],
'with_join' == $with,
$this->options['with_cache'] ?? false
);
}
}
// 模型数据集转换
return $this->model->toCollection($resultSet);
}
/**
* 查询数据转换为模型对象
*
* @param array $result 查询数据
*
* @return void
*/
protected function resultToModel(array &$result): void
{
// JSON数据处理
if (!empty($this->options['json'])) {
$this->jsonModelResult($result);
}
// 实时读取延迟数据
if (!empty($this->options['lazy_fields'])) {
$id = $this->getKey($result);
foreach ($this->options['lazy_fields'] as $field) {
$result[$field] += $this->getLazyFieldValue($field, $id);
}
}
$result = $this->model->newInstance(
$result,
!empty($this->options['is_resultSet']) ? null : $this->getModelUpdateCondition($this->options),
$this->options
);
// 模型数据处理
foreach ($this->options['filter'] as $filter) {
call_user_func_array($filter, [$result, $this->options]);
}
// 关联查询
if (!empty($this->options['relation'])) {
$result->relationQuery($this->options['relation'], $this->options['with_relation_attr']);
}
// 关联预载入查询
if (empty($this->options['is_resultSet'])) {
foreach (['with', 'with_join'] as $with) {
if (!empty($this->options[$with])) {
$result->eagerlyResult(
$this->options[$with],
$this->options['with_relation_attr'],
'with_join' == $with,
$this->options['with_cache'] ?? false
);
}
}
}
// 关联统计查询
if (!empty($this->options['with_aggregate'])) {
foreach ($this->options['with_aggregate'] as $val) {
$result->relationCount($this, $val[0], $val[1], $val[2], false);
}
}
// 动态获取器
if (!empty($this->options['with_attr'])) {
$result->withAttr($this->options['with_attr']);
}
foreach (['hidden', 'visible', 'append'] as $name) {
if (!empty($this->options[$name])) {
$result->$name($this->options[$name]);
}
}
// 刷新原始数据
$result->refreshOrigin();
}
}
@@ -0,0 +1,144 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\concern;
use think\db\Connection;
/**
* 参数绑定支持
*/
trait ParamsBind
{
/**
* 当前参数绑定.
*
* @var array
*/
protected $bind = [];
/**
* 批量参数绑定.
*
* @param array $value 绑定变量值
*
* @return $this
*/
public function bind(array $value)
{
$this->bind = array_merge($this->bind, $value);
return $this;
}
/**
* 单个参数绑定.
*
* @param mixed $value 绑定变量值
* @param int $type 绑定类型
* @param string $name 绑定标识
*
* @return string
*/
public function bindValue($value, int $type = null, string $name = null)
{
$name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_';
$this->bind[$name] = [$value, $type ?: Connection::PARAM_STR];
return $name;
}
/**
* 检测参数是否已经绑定.
*
* @param string $key 参数名
*
* @return bool
*/
public function isBind(string $key)
{
return isset($this->bind[$key]);
}
/**
* 设置自动参数绑定.
*
* @param bool $bind 是否自动参数绑定
*
* @return $this
*/
public function autoBind(bool $bind)
{
$this->options['auto_bind'] = $bind;
return $this;
}
/**
* 检测是否开启自动参数绑定.
*
* @return bool
*/
public function isAutoBind(): bool
{
$autoBind = $this->getConfig('auto_param_bind');
if (null !== $this->getOptions('auto_bind')) {
$autoBind = $this->getOptions('auto_bind');
}
return (bool) $autoBind;
}
/**
* 参数绑定.
*
* @param string $sql 绑定的sql表达式
* @param array $bind 参数绑定
*
* @return void
*/
public function bindParams(string &$sql, array $bind = []): void
{
foreach ($bind as $key => $value) {
if (is_array($value)) {
$name = $this->bindValue($value[0], $value[1], $value[2] ?? null);
} else {
$name = $this->bindValue($value);
}
if (is_numeric($key)) {
$sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
} else {
$sql = str_replace(':' . $key, ':' . $name, $sql);
}
}
}
/**
* 获取绑定的参数 并清空.
*
* @param bool $clear 是否清空绑定数据
*
* @return array
*/
public function getBind(bool $clear = true): array
{
$bind = $this->bind;
if ($clear) {
$this->bind = [];
}
return $bind;
}
}
@@ -0,0 +1,254 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\concern;
use Closure;
use think\Collection;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\db\Query;
use think\helper\Str;
use think\Model;
/**
* 查询数据处理.
*/
trait ResultOperation
{
/**
* 设置数据处理(支持模型).
*
* @param callable $filter 数据处理Callable
* @param string $index 索引(唯一)
*
* @return $this
*/
public function filter(callable $filter, string $index = null)
{
if ($index) {
$this->options['filter'][$index] = $filter;
} else {
$this->options['filter'][] = $filter;
}
return $this;
}
/**
* 是否允许返回空数据(或空模型).
*
* @param bool $allowEmpty 是否允许为空
*
* @return $this
*/
public function allowEmpty(bool $allowEmpty = true)
{
$this->options['allow_empty'] = $allowEmpty;
return $this;
}
/**
* 设置查询数据不存在是否抛出异常.
*
* @param bool $fail 数据不存在是否抛出异常
*
* @return $this
*/
public function failException(bool $fail = true)
{
$this->options['fail'] = $fail;
return $this;
}
/**
* 处理数据.
*
* @param array $result 查询数据
*
* @return void
*/
protected function result(array &$result): void
{
// JSON数据处理
if (!empty($this->options['json'])) {
$this->jsonResult($result);
}
// 实时读取延迟数据
if (!empty($this->options['lazy_fields'])) {
$id = $this->getKey($result);
foreach ($this->options['lazy_fields'] as $field) {
$result[$field] += $this->getLazyFieldValue($field, $id);
}
}
// 查询数据处理
foreach ($this->options['filter'] as $filter) {
$result = call_user_func_array($filter, [$result, $this->options]);
}
// 获取器
if (!empty($this->options['with_attr'])) {
$this->getResultAttr($result, $this->options['with_attr']);
}
}
/**
* 处理数据集.
*
* @param array $resultSet 数据集
* @param bool $toCollection 是否转为对象
*
* @return void
*/
protected function resultSet(array &$resultSet, bool $toCollection = true): void
{
foreach ($resultSet as &$result) {
$this->result($result);
}
// 返回Collection对象
if ($toCollection) {
$resultSet = new Collection($resultSet);
}
}
/**
* 使用获取器处理数据.
*
* @param array $result 查询数据
* @param array $withAttr 字段获取器
*
* @return void
*/
protected function getResultAttr(array &$result, array $withAttr = []): void
{
foreach ($withAttr as $name => $closure) {
$name = Str::snake($name);
if (str_contains($name, '.')) {
// 支持JSON字段 获取器定义
[$key, $field] = explode('.', $name);
if (isset($result[$key])) {
$result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
}
} else {
$result[$name] = $closure($result[$name] ?? null, $result);
}
}
}
/**
* 处理空数据.
*
* @throws DbException
* @throws ModelNotFoundException
* @throws DataNotFoundException
*
* @return array|Model|null|static
*/
protected function resultToEmpty()
{
if (!empty($this->options['fail'])) {
$this->throwNotFound();
} elseif (!empty($this->options['allow_empty'])) {
return !empty($this->model) ? $this->model->newInstance() : [];
}
}
/**
* 查找单条记录 不存在返回空数据(或者空模型).
*
* @param mixed $data 数据
*
* @return array|Model|static|mixed
*/
public function findOrEmpty($data = null)
{
return $this->allowEmpty(true)->find($data);
}
/**
* JSON字段数据转换.
*
* @param array $result 查询数据
*
* @return void
*/
protected function jsonResult(array &$result): void
{
foreach ($this->options['json'] as $name) {
if (!isset($result[$name])) {
continue;
}
$result[$name] = json_decode($result[$name], true);
}
}
/**
* 查询失败 抛出异常.
*
* @throws ModelNotFoundException
* @throws DataNotFoundException
*
* @return void
*/
protected function throwNotFound(): void
{
if (!empty($this->model)) {
$class = get_class($this->model);
throw new ModelNotFoundException('model data Not Found:'.$class, $class, $this->options);
}
$table = $this->getTable();
throw new DataNotFoundException('table data not Found:'.$table, $table, $this->options);
}
/**
* 查找多条记录 如果不存在则抛出异常.
*
* @param array|string|Query|Closure $data 数据
*
* @throws ModelNotFoundException
* @throws DataNotFoundException
*
* @return array|Collection|static[]
*/
public function selectOrFail($data = [])
{
return $this->failException(true)->select($data);
}
/**
* 查找单条记录 如果不存在则抛出异常.
*
* @param array|string|Query|Closure $data 数据
*
* @throws ModelNotFoundException
* @throws DataNotFoundException
*
* @return array|Model|static|mixed
*/
public function findOrFail($data = null)
{
return $this->failException(true)->find($data);
}
}
@@ -0,0 +1,102 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\concern;
/**
* 数据字段信息.
*/
trait TableFieldInfo
{
/**
* 获取数据表字段信息.
*
* @param string $tableName 数据表名
*
* @return array
*/
public function getTableFields(string $tableName = ''): array
{
if ('' == $tableName) {
$tableName = $this->getTable();
}
return $this->connection->getTableFields($tableName);
}
/**
* 获取详细字段类型信息.
*
* @param string $tableName 数据表名称
*
* @return array
*/
public function getFields(string $tableName = ''): array
{
return $this->connection->getFields($tableName ?: $this->getTable());
}
/**
* 获取字段类型信息.
*
* @return array
*/
public function getFieldsType(): array
{
if (!empty($this->options['field_type'])) {
return $this->options['field_type'];
}
return $this->connection->getFieldsType($this->getTable());
}
/**
* 获取字段类型信息.
*
* @param string $field 字段名
*
* @return string|null
*/
public function getFieldType(string $field)
{
$fieldType = $this->getFieldsType();
return $fieldType[$field] ?? null;
}
/**
* 获取字段类型信息.
*
* @return array
*/
public function getFieldsBindType(): array
{
$fieldType = $this->getFieldsType();
return array_map([$this->connection, 'getFieldBindType'], $fieldType);
}
/**
* 获取字段类型信息.
*
* @param string $field 字段名
*
* @return int
*/
public function getFieldBindType(string $field): int
{
$fieldType = $this->getFieldType($field);
return $this->connection->getFieldBindType($fieldType ?: '');
}
}
@@ -0,0 +1,223 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\concern;
/**
* 时间查询支持
*/
trait TimeFieldQuery
{
/**
* 日期查询表达式.
*
* @var array
*/
protected $timeRule = [
'today' => ['today', 'tomorrow -1second'],
'yesterday' => ['yesterday', 'today -1second'],
'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'],
'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'],
'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'],
'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'],
'year' => ['this year 1/1', 'next year 1/1 -1second'],
'last year' => ['last year 1/1', 'this year 1/1 -1second'],
];
/**
* 添加日期或者时间查询规则.
*
* @param array $rule 时间表达式
*
* @return $this
*/
public function timeRule(array $rule)
{
$this->timeRule = array_merge($this->timeRule, $rule);
return $this;
}
/**
* 查询日期或者时间.
*
* @param string $field 日期字段名
* @param string $op 比较运算符或者表达式
* @param mixed $range 比较范围
* @param string $logic AND OR
*
* @return $this
*/
public function whereTime(string $field, string $op, $range = null, string $logic = 'AND')
{
if (is_null($range)) {
$range = $this->timeRule[$op] ?? $op;
$op = is_array($range) ? 'between' : '>=';
}
return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
}
/**
* 查询某个时间间隔数据.
*
* @param string $field 日期字段名
* @param string $start 开始时间
* @param string $interval 时间间隔单位 day/month/year/week/hour/minute/second
* @param int $step 间隔
* @param string $logic AND OR
*
* @return $this
*/
public function whereTimeInterval(string $field, string $start, string $interval = 'day', int $step = 1, string $logic = 'AND')
{
$startTime = strtotime($start);
$endTime = strtotime(($step > 0 ? '+' : '-') . abs($step) . ' ' . $interval . (abs($step) > 1 ? 's' : ''), $startTime);
return $this->whereTime($field, 'between', $step > 0 ? [$startTime, $endTime - 1] : [$endTime, $startTime - 1], $logic);
}
/**
* 查询月数据 whereMonth('time_field', '2018-1').
*
* @param string $field 日期字段名
* @param string $month 月份信息
* @param int $step 间隔
* @param string $logic AND OR
*
* @return $this
*/
public function whereMonth(string $field, string $month = 'this month', int $step = 1, string $logic = 'AND')
{
if (in_array($month, ['this month', 'last month'])) {
$month = date('Y-m', strtotime($month));
}
return $this->whereTimeInterval($field, $month, 'month', $step, $logic);
}
/**
* 查询周数据 whereWeek('time_field', '2018-1-1') 从2018-1-1开始的一周数据.
*
* @param string $field 日期字段名
* @param string $week 周信息
* @param int $step 间隔
* @param string $logic AND OR
*
* @return $this
*/
public function whereWeek(string $field, string $week = 'this week', int $step = 1, string $logic = 'AND')
{
if (in_array($week, ['this week', 'last week'])) {
$week = date('Y-m-d', strtotime($week));
}
return $this->whereTimeInterval($field, $week, 'week', $step, $logic);
}
/**
* 查询年数据 whereYear('time_field', '2018').
*
* @param string $field 日期字段名
* @param string $year 年份信息
* @param int $step 间隔
* @param string $logic AND OR
*
* @return $this
*/
public function whereYear(string $field, string $year = 'this year', int $step = 1, string $logic = 'AND')
{
if (in_array($year, ['this year', 'last year'])) {
$year = date('Y', strtotime($year));
}
return $this->whereTimeInterval($field, $year . '-1-1', 'year', $step, $logic);
}
/**
* 查询日数据 whereDay('time_field', '2018-1-1').
*
* @param string $field 日期字段名
* @param string $day 日期信息
* @param int $step 间隔
* @param string $logic AND OR
*
* @return $this
*/
public function whereDay(string $field, string $day = 'today', int $step = 1, string $logic = 'AND')
{
if (in_array($day, ['today', 'yesterday'])) {
$day = date('Y-m-d', strtotime($day));
}
return $this->whereTimeInterval($field, $day, 'day', $step, $logic);
}
/**
* 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15').
*
* @param string $field 日期字段名
* @param string|int $startTime 开始时间
* @param string|int $endTime 结束时间
* @param string $logic AND OR
*
* @return $this
*/
public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND')
{
return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
}
/**
* 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15').
*
* @param string $field 日期字段名
* @param string|int $startTime 开始时间
* @param string|int $endTime 结束时间
*
* @return $this
*/
public function whereNotBetweenTime(string $field, $startTime, $endTime)
{
return $this->whereTime($field, '<', $startTime)
->whereTime($field, '>', $endTime, 'OR');
}
/**
* 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time').
*
* @param string $startField 开始时间字段
* @param string $endField 结束时间字段
*
* @return $this
*/
public function whereBetweenTimeField(string $startField, string $endField)
{
return $this->whereTime($startField, '<=', time())
->whereTime($endField, '>=', time());
}
/**
* 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time').
*
* @param string $startField 开始时间字段
* @param string $endField 结束时间字段
*
* @return $this
*/
public function whereNotBetweenTimeField(string $startField, string $endField)
{
return $this->whereTime($startField, '>', time())
->whereTime($endField, '<', time(), 'OR');
}
}
@@ -0,0 +1,131 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\concern;
/**
* 事务支持
*/
trait Transaction
{
/**
* 执行数据库Xa事务
*
* @param callable $callback 数据操作方法回调
* @param array $dbs 多个查询对象或者连接对象
*
* @throws \PDOException
* @throws \Exception
* @throws \Throwable
*
* @return mixed
*/
public function transactionXa(callable $callback, array $dbs = [])
{
return $this->connection->transactionXa($callback, $dbs);
}
/**
* 执行数据库事务
*
* @param callable $callback 数据操作方法回调
*
* @return mixed
*/
public function transaction(callable $callback)
{
return $this->connection->transaction($callback);
}
/**
* 启动事务
*
* @return void
*/
public function startTrans(): void
{
$this->connection->startTrans();
}
/**
* 用于非自动提交状态下面的查询提交.
*
* @throws \PDOException
*
* @return void
*/
public function commit(): void
{
$this->connection->commit();
}
/**
* 事务回滚.
*
* @throws \PDOException
*
* @return void
*/
public function rollback(): void
{
$this->connection->rollback();
}
/**
* 启动XA事务
*
* @param string $xid XA事务id
*
* @return void
*/
public function startTransXa(string $xid): void
{
$this->connection->startTransXa($xid);
}
/**
* 预编译XA事务
*
* @param string $xid XA事务id
*
* @return void
*/
public function prepareXa(string $xid): void
{
$this->connection->prepareXa($xid);
}
/**
* 提交XA事务
*
* @param string $xid XA事务id
*
* @return void
*/
public function commitXa(string $xid): void
{
$this->connection->commitXa($xid);
}
/**
* 回滚XA事务
*
* @param string $xid XA事务id
*
* @return void
*/
public function rollbackXa(string $xid): void
{
$this->connection->rollbackXa($xid);
}
}
@@ -0,0 +1,570 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\concern;
use Closure;
use think\db\BaseQuery;
use think\db\Raw;
trait WhereQuery
{
/**
* 指定AND查询条件.
*
* @param mixed $field 查询字段
* @param mixed $op 查询表达式
* @param mixed $condition 查询条件
*
* @return $this
*/
public function where($field, $op = null, $condition = null)
{
if ($field instanceof $this) {
$this->parseQueryWhere($field);
return $this;
} elseif (true === $field || 1 === $field) {
$this->options['where']['AND'][] = true;
return $this;
}
$pk = $this->getPk();
if ((is_null($condition) || '=' == $op) && is_string($pk) && $pk == $field ) {
$this->options['key'] = is_null($condition) ? $op : $condition;
}
$param = func_get_args();
array_shift($param);
return $this->parseWhereExp('AND', $field, $op, $condition, $param);
}
/**
* 解析Query对象查询条件.
*
* @param BaseQuery $query 查询对象
*
* @return void
*/
protected function parseQueryWhere(BaseQuery $query): void
{
$this->options['where'] = $query->getOptions('where') ?? [];
$via = $query->getOptions('via');
if ($via) {
foreach ($this->options['where'] as $logic => &$where) {
foreach ($where as $key => &$val) {
if (is_array($val) && !str_contains($val[0], '.')) {
$val[0] = $via . '.' . $val[0];
}
}
}
}
$this->bind($query->getBind(false));
}
/**
* 指定OR查询条件.
*
* @param mixed $field 查询字段
* @param mixed $op 查询表达式
* @param mixed $condition 查询条件
*
* @return $this
*/
public function whereOr($field, $op = null, $condition = null)
{
$param = func_get_args();
array_shift($param);
return $this->parseWhereExp('OR', $field, $op, $condition, $param);
}
/**
* 指定XOR查询条件.
*
* @param mixed $field 查询字段
* @param mixed $op 查询表达式
* @param mixed $condition 查询条件
*
* @return $this
*/
public function whereXor($field, $op = null, $condition = null)
{
$param = func_get_args();
array_shift($param);
return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
}
/**
* 指定Null查询条件.
*
* @param mixed $field 查询字段
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereNull(string $field, string $logic = 'AND')
{
return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
}
/**
* 指定NotNull查询条件.
*
* @param mixed $field 查询字段
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereNotNull(string $field, string $logic = 'AND')
{
return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
}
/**
* 指定Exists查询条件.
*
* @param mixed $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereExists($condition, string $logic = 'AND')
{
if (is_string($condition)) {
$condition = new Raw($condition);
}
$this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
return $this;
}
/**
* 指定NotExists查询条件.
*
* @param mixed $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereNotExists($condition, string $logic = 'AND')
{
if (is_string($condition)) {
$condition = new Raw($condition);
}
$this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
return $this;
}
/**
* 指定In查询条件.
*
* @param mixed $field 查询字段
* @param mixed $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereIn(string $field, $condition, string $logic = 'AND')
{
return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
}
/**
* 指定NotIn查询条件.
*
* @param mixed $field 查询字段
* @param mixed $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereNotIn(string $field, $condition, string $logic = 'AND')
{
return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
}
/**
* 指定Like查询条件.
*
* @param mixed $field 查询字段
* @param mixed $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereLike(string $field, $condition, string $logic = 'AND')
{
return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
}
/**
* 指定NotLike查询条件.
*
* @param mixed $field 查询字段
* @param mixed $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereNotLike(string $field, $condition, string $logic = 'AND')
{
return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
}
/**
* 指定Between查询条件.
*
* @param mixed $field 查询字段
* @param mixed $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereBetween(string $field, $condition, string $logic = 'AND')
{
return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
}
/**
* 指定NotBetween查询条件.
*
* @param mixed $field 查询字段
* @param mixed $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereNotBetween(string $field, $condition, string $logic = 'AND')
{
return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
}
/**
* 指定FIND_IN_SET查询条件.
*
* @param mixed $field 查询字段
* @param mixed $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereFindInSet(string $field, $condition, string $logic = 'AND')
{
return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true);
}
/**
* 比较两个字段.
*
* @param string $field1 查询字段
* @param string $operator 比较操作符
* @param string $field2 比较字段
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND')
{
if (is_null($field2)) {
$field2 = $operator;
$operator = '=';
}
return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
}
/**
* 设置软删除字段及条件.
*
* @param string $field 查询字段
* @param mixed $condition 查询条件
*
* @return $this
*/
public function useSoftDelete(string $field, $condition = null)
{
if ($field) {
$this->options['soft_delete'] = [$field, $condition];
}
return $this;
}
/**
* 指定Exp查询条件.
*
* @param mixed $field 查询字段
* @param string $where 查询条件
* @param array $bind 参数绑定
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND')
{
$this->options['where'][$logic][] = [$field, 'EXP', new Raw($where, $bind)];
return $this;
}
/**
* 指定字段Raw查询.
*
* @param string $field 查询字段表达式
* @param mixed $op 查询表达式
* @param string $condition 查询条件
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND')
{
if (is_null($condition)) {
$condition = $op;
$op = '=';
}
$this->options['where'][$logic][] = [new Raw($field), $op, $condition];
return $this;
}
/**
* 指定表达式查询条件.
*
* @param string $where 查询条件
* @param array $bind 参数绑定
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function whereRaw(string $where, array $bind = [], string $logic = 'AND')
{
$this->options['where'][$logic][] = new Raw($where, $bind);
return $this;
}
/**
* 指定表达式查询条件 OR.
*
* @param string $where 查询条件
* @param array $bind 参数绑定
*
* @return $this
*/
public function whereOrRaw(string $where, array $bind = [])
{
return $this->whereRaw($where, $bind, 'OR');
}
/**
* 分析查询表达式.
*
* @param string $logic 查询逻辑 and or xor
* @param mixed $field 查询字段
* @param mixed $op 查询表达式
* @param mixed $condition 查询条件
* @param array $param 查询参数
* @param bool $strict 严格模式
*
* @return $this
*/
protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
{
$logic = strtoupper($logic);
if (is_string($field) && !empty($this->options['via']) && !str_contains($field, '.')) {
$field = $this->options['via'] . '.' . $field;
}
if ($strict) {
// 使用严格模式查询
if ('=' == $op) {
$where = $this->whereEq($field, $condition);
} else {
$where = [$field, $op, $condition, $logic];
}
} elseif (is_array($field)) {
// 解析数组批量查询
return $this->parseArrayWhereItems($field, $logic);
} elseif ($field instanceof Closure) {
$where = $field;
} elseif (is_string($field)) {
if ($condition instanceof Raw) {
} elseif (preg_match('/[,=\<\'\"\(\s]/', $field)) {
return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
} elseif (is_string($op) && strtolower($op) == 'exp' && !is_null($condition)) {
$bind = isset($param[2]) && is_array($param[2]) ? $param[2] : [];
return $this->whereExp($field, $condition, $bind, $logic);
}
$where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
}
if (!empty($where)) {
$this->options['where'][$logic][] = $where;
}
return $this;
}
/**
* 分析查询表达式.
*
* @param string $logic 查询逻辑 and or xor
* @param mixed $field 查询字段
* @param mixed $op 查询表达式
* @param mixed $condition 查询条件
* @param array $param 查询参数
*
* @return array
*/
protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array
{
if (is_array($op)) {
// 同一字段多条件查询
array_unshift($param, $field);
$where = $param;
} elseif ($field && is_null($condition)) {
if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
// null查询
$where = [$field, $op, ''];
} elseif ('=' === $op || is_null($op)) {
$where = [$field, 'NULL', ''];
} elseif ('<>' === $op) {
$where = [$field, 'NOTNULL', ''];
} else {
// 字段相等查询
$where = $this->whereEq($field, $op);
}
} elseif (is_string($op) && in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
$where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition];
} else {
$where = $field ? [$field, $op, $condition, $param[2] ?? null] : [];
}
return $where;
}
/**
* 相等查询的主键处理.
*
* @param string $field 字段名
* @param mixed $value 字段值
*
* @return array
*/
protected function whereEq(string $field, $value): array
{
if ($this->getPk() == $field) {
$this->options['key'] = $value;
}
return [$field, '=', $value];
}
/**
* 数组批量查询.
*
* @param array $field 批量查询
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
protected function parseArrayWhereItems(array $field, string $logic)
{
$where = [];
foreach ($field as $key => $val) {
if (is_int($key)) {
$where[] = $val;
} elseif ($val instanceof Raw) {
$where[] = [$key, 'exp', $val];
} else {
$where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val];
}
}
if (!empty($where)) {
$this->options['where'][$logic] = isset($this->options['where'][$logic]) ?
array_merge($this->options['where'][$logic], $where) : $where;
}
return $this;
}
/**
* 去除某个查询条件.
*
* @param string $field 查询字段
* @param string $logic 查询逻辑 and or xor
*
* @return $this
*/
public function removeWhereField(string $field, string $logic = 'AND')
{
$logic = strtoupper($logic);
if (isset($this->options['where'][$logic])) {
foreach ($this->options['where'][$logic] as $key => $val) {
if (is_array($val) && $val[0] == $field) {
unset($this->options['where'][$logic][$key]);
}
}
}
return $this;
}
/**
* 条件查询.
*
* @param mixed $condition 满足条件(支持闭包)
* @param Closure|array $query 满足条件后执行的查询表达式(闭包或数组)
* @param Closure|array $otherwise 不满足条件后执行
*
* @return $this
*/
public function when($condition, $query, $otherwise = null)
{
if ($condition instanceof Closure) {
$condition = $condition($this);
}
if ($condition) {
if ($query instanceof Closure) {
$query($this, $condition);
} elseif (is_array($query)) {
$this->where($query);
}
} elseif ($otherwise) {
if ($otherwise instanceof Closure) {
$otherwise($this, $condition);
} elseif (is_array($otherwise)) {
$this->where($otherwise);
}
}
return $this;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,169 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\connector;
use PDO;
use think\db\PDOConnection;
/**
* mysql数据库驱动.
*/
class Mysql extends PDOConnection
{
/**
* 解析pdo连接的dsn信息.
*
* @param array $config 连接信息
*
* @return string
*/
protected function parseDsn(array $config): string
{
if (!empty($config['socket'])) {
$dsn = 'mysql:unix_socket=' . $config['socket'];
} elseif (!empty($config['hostport'])) {
$dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport'];
} else {
$dsn = 'mysql:host=' . $config['hostname'];
}
$dsn .= ';dbname=' . $config['database'];
if (!empty($config['charset'])) {
$dsn .= ';charset=' . $config['charset'];
}
return $dsn;
}
/**
* 取得数据表的字段信息.
*
* @param string $tableName
*
* @return array
*/
public function getFields(string $tableName): array
{
[$tableName] = explode(' ', $tableName);
if (!str_contains($tableName, '`')) {
if (str_contains($tableName, '.')) {
$tableName = str_replace('.', '`.`', $tableName);
}
$tableName = '`' . $tableName . '`';
}
$sql = 'SHOW FULL COLUMNS FROM ' . $tableName;
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
if (!empty($result)) {
foreach ($result as $key => $val) {
$val = array_change_key_case($val);
$info[$val['field']] = [
'name' => $val['field'],
'type' => $val['type'],
'notnull' => 'NO' == $val['null'],
'default' => $val['default'],
'primary' => strtolower($val['key']) == 'pri',
'autoinc' => strtolower($val['extra']) == 'auto_increment',
'comment' => $val['comment'],
];
}
}
return $this->fieldCase($info);
}
/**
* 取得数据库的表信息.
*
* @param string $dbName
*
* @return array
*/
public function getTables(string $dbName = ''): array
{
$sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES ';
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
foreach ($result as $key => $val) {
$info[$key] = current($val);
}
return $info;
}
protected function supportSavepoint(): bool
{
return true;
}
/**
* 启动XA事务
*
* @param string $xid XA事务id
*
* @return void
*/
public function startTransXa(string $xid): void
{
$this->initConnect(true);
$this->linkID->exec("XA START '$xid'");
}
/**
* 预编译XA事务
*
* @param string $xid XA事务id
*
* @return void
*/
public function prepareXa(string $xid): void
{
$this->initConnect(true);
$this->linkID->exec("XA END '$xid'");
$this->linkID->exec("XA PREPARE '$xid'");
}
/**
* 提交XA事务
*
* @param string $xid XA事务id
*
* @return void
*/
public function commitXa(string $xid): void
{
$this->initConnect(true);
$this->linkID->exec("XA COMMIT '$xid'");
}
/**
* 回滚XA事务
*
* @param string $xid XA事务id
*
* @return void
*/
public function rollbackXa(string $xid): void
{
$this->initConnect(true);
$this->linkID->exec("XA ROLLBACK '$xid'");
}
}
@@ -0,0 +1,124 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\db\connector;
use PDO;
use think\db\BaseQuery;
use think\db\PDOConnection;
/**
* Oracle数据库驱动.
*/
class Oracle extends PDOConnection
{
/**
* 解析pdo连接的dsn信息.
*
* @param array $config 连接信息
*
* @return string
*/
protected function parseDsn(array $config): string
{
$dsn = 'oci:dbname=';
if (!empty($config['hostname'])) {
// Oracle Instant Client
$dsn .= '//' . $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/';
}
$dsn .= $config['database'];
if (!empty($config['charset'])) {
$dsn .= ';charset=' . $config['charset'];
}
return $dsn;
}
/**
* 取得数据表的字段信息.
*
* @param string $tableName
*
* @return array
*/
public function getFields(string $tableName): array
{
[$tableName] = explode(' ', $tableName);
$sql = "select a.column_name,data_type,DECODE (nullable, 'Y', 0, 1) notnull,data_default, DECODE (A .column_name,b.column_name,1,0) pk from all_tab_columns a,(select column_name from all_constraints c, all_cons_columns col where c.constraint_name = col.constraint_name and c.constraint_type = 'P' and c.table_name = '" . $tableName . "' ) b where table_name = '" . $tableName . "' and a.column_name = b.column_name (+)";
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
if ($result) {
foreach ($result as $key => $val) {
$val = array_change_key_case($val);
$info[$val['column_name']] = [
'name' => $val['column_name'],
'type' => $val['data_type'],
'notnull' => $val['notnull'],
'default' => $val['data_default'],
'primary' => $val['pk'],
'autoinc' => $val['pk'],
];
}
}
return $this->fieldCase($info);
}
/**
* 取得数据库的表信息(暂时实现取得用户表信息).
*
* @param string $dbName
*
* @return array
*/
public function getTables(string $dbName = ''): array
{
$sql = 'select table_name from all_tables';
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
foreach ($result as $key => $val) {
$info[$key] = current($val);
}
return $info;
}
/**
* 获取最近插入的ID.
*
* @param BaseQuery $query 查询对象
* @param string|null $sequence 自增序列名
*
* @return mixed
*/
public function getLastInsID(BaseQuery $query, string $sequence = null)
{
if (!is_null($sequence)) {
$pdo = $this->linkID->query("select {$sequence}.currval as id from dual");
$result = $pdo->fetchColumn();
}
return $result ?? null;
}
protected function supportSavepoint(): bool
{
return true;
}
}
@@ -0,0 +1,111 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\db\connector;
use PDO;
use think\db\PDOConnection;
/**
* Pgsql数据库驱动.
*/
class Pgsql extends PDOConnection
{
/**
* 默认PDO连接参数.
*
* @var array
*/
protected $params = [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
];
/**
* 解析pdo连接的dsn信息.
*
* @param array $config 连接信息
*
* @return string
*/
protected function parseDsn(array $config): string
{
$dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname'];
if (!empty($config['hostport'])) {
$dsn .= ';port=' . $config['hostport'];
}
return $dsn;
}
/**
* 取得数据表的字段信息.
*
* @param string $tableName
*
* @return array
*/
public function getFields(string $tableName): array
{
[$tableName] = explode(' ', $tableName);
$sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');';
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
if (!empty($result)) {
foreach ($result as $key => $val) {
$val = array_change_key_case($val);
$info[$val['field']] = [
'name' => $val['field'],
'type' => $val['type'],
'notnull' => (bool) ('' !== $val['null']),
'default' => $val['default'],
'primary' => !empty($val['key']),
'autoinc' => str_starts_with((string) $val['extra'], 'nextval('),
];
}
}
return $this->fieldCase($info);
}
/**
* 取得数据库的表信息.
*
* @param string $dbName
*
* @return array
*/
public function getTables(string $dbName = ''): array
{
$sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'";
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
foreach ($result as $key => $val) {
$info[$key] = current($val);
}
return $info;
}
protected function supportSavepoint(): bool
{
return true;
}
}
@@ -0,0 +1,97 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\db\connector;
use PDO;
use think\db\PDOConnection;
/**
* Sqlite数据库驱动.
*/
class Sqlite extends PDOConnection
{
/**
* 解析pdo连接的dsn信息.
*
* @param array $config 连接信息
*
* @return string
*/
protected function parseDsn(array $config): string
{
return 'sqlite:' . $config['database'];
}
/**
* 取得数据表的字段信息.
*
* @param string $tableName
*
* @return array
*/
public function getFields(string $tableName): array
{
[$tableName] = explode(' ', $tableName);
$sql = 'PRAGMA table_info( \'' . $tableName . '\' )';
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
if (!empty($result)) {
foreach ($result as $key => $val) {
$val = array_change_key_case($val);
$info[$val['name']] = [
'name' => $val['name'],
'type' => $val['type'],
'notnull' => 1 === $val['notnull'],
'default' => $val['dflt_value'],
'primary' => '1' == $val['pk'],
'autoinc' => '1' == $val['pk'],
];
}
}
return $this->fieldCase($info);
}
/**
* 取得数据库的表信息.
*
* @param string $dbName
*
* @return array
*/
public function getTables(string $dbName = ''): array
{
$sql = "SELECT name FROM sqlite_master WHERE type='table' "
. 'UNION ALL SELECT name FROM sqlite_temp_master '
. "WHERE type='table' ORDER BY name";
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
foreach ($result as $key => $val) {
$info[$key] = current($val);
}
return $info;
}
protected function supportSavepoint(): bool
{
return true;
}
}
@@ -0,0 +1,130 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\db\connector;
use PDO;
use think\db\PDOConnection;
/**
* Sqlsrv数据库驱动.
*/
class Sqlsrv extends PDOConnection
{
/**
* 默认PDO连接参数.
*
* @var array
*/
protected $params = [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
];
/**
* 解析pdo连接的dsn信息.
*
* @param array $config 连接信息
*
* @return string
*/
protected function parseDsn(array $config): string
{
$dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname'];
if (!empty($config['hostport'])) {
$dsn .= ',' . $config['hostport'];
}
if (!empty($config['trust_server_certificate'])) {
$dsn .= ';TrustServerCertificate=' . $config['trust_server_certificate'];
}
return $dsn;
}
/**
* 取得数据表的字段信息.
*
* @param string $tableName
*
* @return array
*/
public function getFields(string $tableName): array
{
[$tableName] = explode(' ', $tableName);
str_contains($tableName, '.') && $tableName = substr($tableName, strpos($tableName, '.') + 1);
$sql = "SELECT column_name, data_type, column_default, is_nullable
FROM information_schema.tables AS t
JOIN information_schema.columns AS c
ON t.table_catalog = c.table_catalog
AND t.table_schema = c.table_schema
AND t.table_name = c.table_name
WHERE t.table_name = '$tableName'";
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
if (!empty($result)) {
foreach ($result as $key => $val) {
$val = array_change_key_case($val);
$info[$val['column_name']] = [
'name' => $val['column_name'],
'type' => $val['data_type'],
'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes
'default' => $val['column_default'],
'primary' => false,
'autoinc' => false,
];
}
}
$sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'";
$pdo = $this->linkID->query($sql);
$result = $pdo->fetch(PDO::FETCH_ASSOC);
if ($result) {
$info[$result['column_name']]['primary'] = true;
}
return $this->fieldCase($info);
}
/**
* 取得数据表的字段信息.
*
* @param string $dbName
*
* @return array
*/
public function getTables(string $dbName = ''): array
{
$sql = "SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
";
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
foreach ($result as $key => $val) {
$info[$key] = current($val);
}
return $info;
}
}
@@ -0,0 +1,117 @@
CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS
$BODY$
DECLARE
v_type varchar;
BEGIN
IF a_type='int8' THEN
v_type:='bigint';
ELSIF a_type='int4' THEN
v_type:='integer';
ELSIF a_type='int2' THEN
v_type:='smallint';
ELSIF a_type='bpchar' THEN
v_type:='char';
ELSE
v_type:=a_type;
END IF;
RETURN v_type;
END;
$BODY$
LANGUAGE PLPGSQL;
CREATE TYPE "public"."tablestruct" AS (
"fields_key_name" varchar(100),
"fields_name" VARCHAR(200),
"fields_type" VARCHAR(20),
"fields_length" BIGINT,
"fields_not_null" VARCHAR(10),
"fields_default" VARCHAR(500),
"fields_comment" VARCHAR(1000)
);
CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
$body$
DECLARE
v_ret tablestruct;
v_oid oid;
v_sql varchar;
v_rec RECORD;
v_key varchar;
BEGIN
SELECT
pg_class.oid INTO v_oid
FROM
pg_class
INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name)
WHERE
pg_class.relname=a_table_name;
IF NOT FOUND THEN
RETURN;
END IF;
v_sql='
SELECT
pg_attribute.attname AS fields_name,
pg_attribute.attnum AS fields_index,
pgsql_type(pg_type.typname::varchar) AS fields_type,
pg_attribute.atttypmod-4 as fields_length,
CASE WHEN pg_attribute.attnotnull THEN ''not null''
ELSE ''''
END AS fields_not_null,
pg_attrdef.adsrc AS fields_default,
pg_description.description AS fields_comment
FROM
pg_attribute
INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid
INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid
LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum
LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum
WHERE
pg_attribute.attnum > 0
AND attisdropped <> ''t''
AND pg_class.oid = ' || v_oid || '
ORDER BY pg_attribute.attnum' ;
FOR v_rec IN EXECUTE v_sql LOOP
v_ret.fields_name=v_rec.fields_name;
v_ret.fields_type=v_rec.fields_type;
IF v_rec.fields_length > 0 THEN
v_ret.fields_length:=v_rec.fields_length;
ELSE
v_ret.fields_length:=NULL;
END IF;
v_ret.fields_not_null=v_rec.fields_not_null;
v_ret.fields_default=v_rec.fields_default;
v_ret.fields_comment=v_rec.fields_comment;
SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name;
IF FOUND THEN
v_ret.fields_key_name=v_key;
ELSE
v_ret.fields_key_name='';
END IF;
RETURN NEXT v_ret;
END LOOP;
RETURN ;
END;
$body$
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar)
IS '获得表信息';
---重载一个函数
CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
$body$
DECLARE
v_ret tablestruct;
BEGIN
FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP
RETURN NEXT v_ret;
END LOOP;
RETURN;
END;
$body$
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar)
IS '获得表信息';
@@ -0,0 +1,117 @@
CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS
$BODY$
DECLARE
v_type varchar;
BEGIN
IF a_type='int8' THEN
v_type:='bigint';
ELSIF a_type='int4' THEN
v_type:='integer';
ELSIF a_type='int2' THEN
v_type:='smallint';
ELSIF a_type='bpchar' THEN
v_type:='char';
ELSE
v_type:=a_type;
END IF;
RETURN v_type;
END;
$BODY$
LANGUAGE PLPGSQL;
CREATE TYPE "public"."tablestruct" AS (
"fields_key_name" varchar(100),
"fields_name" VARCHAR(200),
"fields_type" VARCHAR(20),
"fields_length" BIGINT,
"fields_not_null" VARCHAR(10),
"fields_default" VARCHAR(500),
"fields_comment" VARCHAR(1000)
);
CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
$body$
DECLARE
v_ret tablestruct;
v_oid oid;
v_sql varchar;
v_rec RECORD;
v_key varchar;
BEGIN
SELECT
pg_class.oid INTO v_oid
FROM
pg_class
INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name)
WHERE
pg_class.relname=a_table_name;
IF NOT FOUND THEN
RETURN;
END IF;
v_sql='
SELECT
pg_attribute.attname AS fields_name,
pg_attribute.attnum AS fields_index,
pgsql_type(pg_type.typname::varchar) AS fields_type,
pg_attribute.atttypmod-4 as fields_length,
CASE WHEN pg_attribute.attnotnull THEN ''not null''
ELSE ''''
END AS fields_not_null,
pg_get_expr(pg_attrdef.adbin, pg_attrdef.adrelid) AS fields_default,
pg_description.description AS fields_comment
FROM
pg_attribute
INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid
INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid
LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum
LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum
WHERE
pg_attribute.attnum > 0
AND attisdropped <> ''t''
AND pg_class.oid = ' || v_oid || '
ORDER BY pg_attribute.attnum' ;
FOR v_rec IN EXECUTE v_sql LOOP
v_ret.fields_name=v_rec.fields_name;
v_ret.fields_type=v_rec.fields_type;
IF v_rec.fields_length > 0 THEN
v_ret.fields_length:=v_rec.fields_length;
ELSE
v_ret.fields_length:=NULL;
END IF;
v_ret.fields_not_null=v_rec.fields_not_null;
v_ret.fields_default=v_rec.fields_default;
v_ret.fields_comment=v_rec.fields_comment;
SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name;
IF FOUND THEN
v_ret.fields_key_name=v_key;
ELSE
v_ret.fields_key_name='';
END IF;
RETURN NEXT v_ret;
END LOOP;
RETURN ;
END;
$body$
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar)
IS '获得表信息';
---重载一个函数
CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
$body$
DECLARE
v_ret tablestruct;
BEGIN
FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP
RETURN NEXT v_ret;
END LOOP;
RETURN;
END;
$body$
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar)
IS '获得表信息';
@@ -0,0 +1,35 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\exception;
/**
* PDO参数绑定异常.
*/
class BindParamException extends DbException
{
/**
* BindParamException constructor.
*
* @param string $message
* @param array $config
* @param string $sql
* @param array $bind
* @param int $code
*/
public function __construct(string $message, array $config, string $sql, array $bind, int $code = 10502)
{
$this->setData('Bind Param', $bind);
parent::__construct($message, $config, $sql, $code);
}
}
@@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\exception;
class DataNotFoundException extends DbException
{
protected $table;
/**
* DbException constructor.
*
* @param string $message
* @param string $table
* @param array $config
*/
public function __construct(string $message, string $table = '', array $config = [])
{
$this->message = $message;
$this->table = $table;
$this->setData('Database Config', $config);
}
/**
* 获取数据表名.
*
* @return string
*/
public function getTable()
{
return $this->table;
}
}
@@ -0,0 +1,20 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\db\exception;
/**
* Db事件异常.
*/
class DbEventException extends DbException
{
}
@@ -0,0 +1,45 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\exception;
use think\Exception;
/**
* Database相关异常处理类.
*/
class DbException extends Exception
{
/**
* DbException constructor.
*
* @param string $message
* @param array $config
* @param string $sql
* @param int $code
*/
public function __construct(string $message, array $config = [], string $sql = '', int $code = 10500)
{
$this->message = $message;
$this->code = $code;
$this->setData('Database Status', [
'Error Code' => $code,
'Error Message' => $message,
'Error SQL' => $sql,
]);
unset($config['username'], $config['password']);
$this->setData('Database Config', $config);
}
}
@@ -0,0 +1,23 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\exception;
use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface;
/**
* 非法数据异常.
*/
class InvalidArgumentException extends \InvalidArgumentException implements SimpleCacheInvalidArgumentInterface
{
}
@@ -0,0 +1,20 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\db\exception;
/**
* 模型事件异常.
*/
class ModelEventException extends DbException
{
}
@@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\exception;
class ModelNotFoundException extends DbException
{
protected $model;
/**
* 构造方法.
*
* @param string $message
* @param string $model
* @param array $config
*/
public function __construct(string $message, string $model = '', array $config = [])
{
$this->message = $message;
$this->model = $model;
$this->setData('Database Config', $config);
}
/**
* 获取模型类名.
*
* @return string
*/
public function getModel()
{
return $this->model;
}
}
@@ -0,0 +1,45 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\db\exception;
/**
* PDO异常处理类
* 重新封装了系统的\PDOException类.
*/
class PDOException extends DbException
{
/**
* PDOException constructor.
*
* @param \PDOException $exception
* @param array $config
* @param string $sql
* @param int $code
*/
public function __construct(\PDOException $exception, array $config = [], string $sql = '', int $code = 10501)
{
$error = $exception->errorInfo;
$message = $exception->getMessage();
if (!empty($error)) {
$this->setData('PDO Error Info', [
'SQLSTATE' => $error[0],
'Driver Error Code' => $error[1] ?? 0,
'Driver Error Message' => $error[2] ?? '',
]);
}
parent::__construct($message, $config, $sql, $code);
}
}