新增
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user