// +---------------------------------------------------------------------- declare (strict_types = 1); namespace think\db; use Closure; use PDOStatement; use ReflectionFunction; 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 float $step 步进值 * @param int $lazyTime 延迟时间(秒) * * @return int|false */ public function setInc(string $field, float $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('inc', $guid, $step, $lazyTime); if (false === $step) { return true; } } return $this->inc($field, $step)->update(); } /** * 字段值减少(支持延迟写入) * * @param string $field 字段名 * @param float $step 步进值 * @param int $lazyTime 延迟时间(秒) * * @return int|false */ public function setDec(string $field, float $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 float $step 写入步进值 * @param int $lazyTime 延时时间(s) * @return false|integer */ protected function lazyWrite(string $type, string $guid, float $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 { if (null === $data) { $data = $this->options; $data['table'] = $this->getConfig('database') . var_export($this->getTable(), true); unset($data['scope'], $data['default_model']); foreach (['AND', 'OR', 'XOR'] as $logic) { if (isset($data['where'][$logic])) { foreach ($data['where'][$logic] as $key => $val) { if ($val instanceof Closure) { $reflection = new ReflectionFunction($val); $properties = $reflection->getStaticVariables(); if (empty($properties)) { $name = $reflection->getName() . $reflection->getStartLine() . '-' . $reflection->getEndLine(); } else { $name = var_export($properties, true); } $data['Closure'][] = $name; unset($data['where'][$logic][$key]); } } } } } return md5(serialize(var_export($data, 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|null $column 分批处理的字段名 * @param string $order 字段排序 * * @throws Exception * * @return bool */ public function chunk(int $count, callable $callback, string | array | null $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; } }