• 仿照 medoo 的方法和 条件查询,内部使用 $wpdb
  • codex-5.6-sol
  • 能用,已经在一个表上面试验了
  • 缺少对象缓存

这里备份一下当时用AI手写的代码

目前项目已经替换为基于BerlinDB库封装WU_DB类,直观的好处可能是自动化注册表升级表?更全面一点?随之而来的是空间占用增加四五百KB ….

<?php
/**
 * 自定义表基础操作方法
 * 使用时继承该类,按需重写方法属性
 *
 * 【基本用法】
 * class Demo_Table extends WU_DB_Table {
 *     protected function config() {
 *         return [
 *             'table_name' => 'demo',
 *         ];
 *     }
 * }
 *
 * 【表名说明】
 * table_name 默认填写不带 $wpdb->prefix 的表名,如 demo 会自动转成 wp_demo。
 * 如果 table_name 已经包含 $wpdb->prefix,则直接使用原表名。
 *
 * 【字段说明】
 * $columns 支持字符串、数组和 "*"。
 * 示例:'*'、'id,title'、['id','title']。
 * 为了避免 SQL 注入,字段名和表名只支持普通 SQL 标识符,不支持表达式和函数。
 *
 * 【$where 查询条件】
 * 简单等于:['user'=>'admin','age'=>'18']
 * NULL 判断:['deleted_at'=>null] 等同于 deleted_at IS NULL
 * IN 查询:['id'=>[1,2,3]] 或 ['id[in]'=>[1,2,3]]
 * NOT IN 查询:['id[!]'=>[1,2,3]] 或 ['id[not in]'=>[1,2,3]]
 *
 * 【比较运算符】
 * [>]    大于
 * [>=]   大于等于
 * [<]    小于
 * [<=]   小于等于
 * [!]    不等于、IS NOT NULL、NOT IN
 * [~]    LIKE 模糊匹配,普通字符串会自动转成 %关键词%
 * [!~]   NOT LIKE
 * [<>]   BETWEEN,在范围内
 * [><]   NOT BETWEEN,不在范围内
 * [REGEXP] 正则匹配
 * [in]   IN 查询
 * [not in] NOT IN 查询
 *
 * 【比较运算符示例】
 * ['user_id[>]' => 100,'age[>=]' => 18]
 * ['title[~]' => 'WordPress']
 * ['title[~]' => ['AND' => ['WordPress','Plugin']]]
 * ['created_at[<>]' => ['2026-01-01','2026-12-31']]
 * ['created_at[><]' => ['2026-01-01','2026-12-31']]
 * ['name[REGEXP]' => '^wu_']
 *
 * 【字段和字段比较】
 * 使用数字索引写法,右侧会被当作字段名而不是字段值。
 * ['created_at[>]updated_at']
 * ['user_id[=]author_id']
 *
 * 【AND / OR 条件】
 * 默认为 AND 条件:['user_id[>]' => 100,'age[>]' => 18]
 * 指定 OR 条件:['OR'=>['status' => 'active','status[!]' => 'pending']]
 * 同级需要多个 AND/OR 分组时,可以加注释后缀:['OR #status'=>[...],'OR #role'=>[...]]
 *
 * 【ORDER / GROUP / HAVING / LIMIT】
 * ORDER:['ORDER' => ['id' => 'DESC','name' => 'ASC']]
 * ORDER 指定排序值:['ORDER' => ['status' => ['publish','draft']]]
 * GROUP:['GROUP' => 'user_id'] 或 ['GROUP' => ['user_id','status']]
 * HAVING:['HAVING' => ['total[>]' => 10]]
 * LIMIT:['LIMIT' => 10] 或 ['LIMIT' => [20,10]],数组表示 OFFSET 20 LIMIT 10
 *
 * 【MATCH 全文检索】
 * ['MATCH' => ['columns' => ['title','content'],'keyword' => 'wordpress','mode' => 'boolean']]
 * mode 可选:natural、natural+query、boolean、query。
 *
 * 【注意事项】
 * 该类只处理单表查询,不支持 join 联表。
 * 所有字段名会被反引号包裹,所有字段值会通过 $wpdb->prepare() 处理。
 * 不支持任意 raw SQL;复杂表达式建议在子类中单独写专用方法。
 *
 * 【可用方法】
 * get($columns, $where = []) 获取一行数据,单字段查询时直接返回字段值。
 * select($columns = '*', $where = []) 获取多行数据。
 * insert($datas) 插入单条或多条数据,返回插入成功的行数。
 * update($data, $where) 更新数据,必须传入有效 WHERE 条件。
 * delete($where) 删除数据,必须传入有效 WHERE 条件。
 * has($where = []) 检查是否存在匹配数据。
 * rand($columns = '*', $where = []) 随机获取数据。
 * count($where = [], $column = '*') 统计行数。
 * max($column, $where = []) 获取字段最大值。
 * min($column, $where = []) 获取字段最小值。
 * avg($column, $where = []) 获取字段平均值。
 * sum($column, $where = []) 获取字段求和值。
 * id() 获取最后插入的自增 ID。
 * last() 获取最后执行的 SQL。
 * log() 返回当前实例执行过的 SQL 列表。
 * */
abstract class WU_DB_Table {
    // WordPress 数据库对象
    protected $wpdb;
    // 当前模型对应的完整数据表名
    protected $table_name = '';
    // 最后一次执行的 SQL
    protected $last_sql = '';
    // 当前实例执行过的 SQL 列表
    protected $logs = [];
    
    public function __construct() {
        global $wpdb;
        $this->wpdb = $wpdb;
        $config = array_merge([
            # 数据表名,不带 $wpdb->prefix
            'table_name' => '',
        ], $this->config());
        
        if (str_starts_with($config['table_name'], $wpdb->prefix)) $this->table_name = $config['table_name'];
        else $this->table_name = $wpdb->prefix . $config['table_name'];
    }
    
    // 子类重写这里配置表
    protected function config() {
        return [];
    }
    
    /***
     * 从表中返回一行数据。注意:这个方法只能获取一条数据。
     * @param $columns  string|array 返回的字段列('*'为全部字段)
     * @param $where    array WHERE 条件
     * @return string|array
     * */
    function get($columns, $where = []) {
        // get 只取一条数据,强制覆盖 LIMIT
        $where = is_array($where) ? $where : [];
        $where['LIMIT'] = 1;
        
        // 构建查询字段和条件
        $fields = $this->build_columns($columns);
        [
            $where_sql,
            $where_values,
        ] = $this->build_where($where);
        
        // 执行查询并记录 SQL
        $sql = "SELECT {$fields} FROM {$this->build_identifier($this->table_name)} {$where_sql}";
        $row = $this->wpdb->get_row($this->query_sql($sql, $where_values), ARRAY_A);
        
        if (empty($row)) return [];
        
        // 单字段查询时直接返回字段值,方便快速取值
        $column = is_string($columns) ? trim($columns) : '';
        if ($column !== '*' && strpos($column, ',') === false && array_key_exists($column, $row)) {
            return $row[$column];
        }
        
        return $row;
    }
    
    /***
     * 数据库查询
     * @param $columns  string|array 返回的字段列
     * @param $where    array WHERE 条件
     * @return array
     * */
    function select($columns = '*', $where = []) {
        // 构建查询字段和条件
        $fields = $this->build_columns($columns);
        [
            $where_sql,
            $where_values,
        ] = $this->build_where($where);
        
        // 执行查询并返回二维数组
        $sql = "SELECT {$fields} FROM {$this->build_identifier($this->table_name)} {$where_sql}";
        
        return $this->wpdb->get_results($this->query_sql($sql, $where_values), ARRAY_A);
    }
    
    
    /**
     * 插入数据
     * @param $datas             array 要插入的数据
     *                           单条数据:['name'=>'xx']
     *                           多条数据:[['name'=>'x1'],['name'=>'x2']]
     * @return number 插入的行数
     * */
    function insert($datas) {
        if (empty($datas) || !is_array($datas)) return 0;
        
        // 兼容单条数据和多条数据,两种写法统一转成二维数组处理
        $rows = isset($datas[0]) && is_array($datas[0]) ? $datas : [$datas];
        $count = 0;
        
        foreach ($rows as $row) {
            if (empty($row) || !is_array($row)) continue;
            
            // 只保留合法字段,避免字段名参与 SQL 注入
            $data = [];
            foreach ($row as $field => $value) {
                if (!$this->is_identifier($field)) continue;
                $data[$field] = $value;
            }
            
            if (empty($data)) continue;
            
            // 交给 $wpdb->insert 处理插入和字段值转义
            $result = $this->wpdb->insert($this->table_name, $data);
            $this->record_sql($this->wpdb->last_query ?? '');
            
            if ($result !== false) $count++;
        }
        
        return $count;
    }
    
    /**
     * 更新数据
     * @param $data  array 要更新的数据
     * @param $where array 更新条件
     * @return number 受影响的行数
     * */
    function update($data, $where) {
        if (empty($data) || !is_array($data) || empty($where)) return 0;
        if (!$this->has_filter_condition($where)) return 0;
        
        // 构建 SET 字段和值
        $sets = [];
        $values = [];
        
        foreach ($data as $field => $value) {
            if (!$this->is_identifier($field)) continue;
            
            if ($value === null) {
                $sets[] = $this->build_identifier($field) . ' = NULL';
                continue;
            }
            
            $sets[] = $this->build_identifier($field) . ' = %s';
            $values[] = $value;
        }
        
        // 构建 WHERE 条件,更新必须带有效条件
        [
            $where_sql,
            $where_values,
        ] = $this->build_where($this->filter_write_where($where));
        if (empty($sets) || empty($where_sql)) return 0;
        
        // 执行更新并返回受影响行数
        $values = array_merge($values, $where_values);
        $sql = "UPDATE {$this->build_identifier($this->table_name)} SET " . implode(', ', $sets) . " {$where_sql}";
        $result = $this->wpdb->query($this->query_sql($sql, $values));
        
        return $result === false ? 0 : (int)$result;
    }
    
    /**
     * 删除数据:出于安全考虑,$where 参数是必需的。
     * @param $where array
     * @return number 受影响的行数
     * */
    function delete($where) {
        if (empty($where)) return 0;
        if (!$this->has_filter_condition($where)) return 0;
        
        // 构建 WHERE 条件,删除必须带有效条件
        [
            $where_sql,
            $where_values,
        ] = $this->build_where($this->filter_write_where($where));
        if (empty($where_sql)) return 0;
        
        // 执行删除并返回受影响行数
        $sql = "DELETE FROM {$this->build_identifier($this->table_name)} {$where_sql}";
        $result = $this->wpdb->query($this->query_sql($sql, $where_values));
        
        return $result === false ? 0 : (int)$result;
    }
    
    /**
     * 检查数据是否存在。
     *
     * @param $where array WHERE 条件
     * @return bool 是否存在匹配数据
     */
    function has($where = []) {
        // 构建 EXISTS 子查询条件
        [
            $where_sql,
            $where_values,
        ] = $this->build_where($where);
        
        // EXISTS 只需要判断是否有数据,不取具体字段
        $sql = "SELECT EXISTS(SELECT 1 FROM {$this->build_identifier($this->table_name)} {$where_sql})";
        $result = $this->wpdb->get_var($this->query_sql($sql, $where_values));
        
        return $result === '1' || $result === 1 || $result === true;
    }
    
    /**
     * 随机获取数据。
     *
     * @param $columns string|array 返回的字段列
     * @param $where   array WHERE 条件
     * @return array 随机排序后的数据列表
     */
    function rand($columns = '*', $where = []) {
        // 构建查询字段和随机排序条件
        $fields = $this->build_columns($columns);
        [
            $where_sql,
            $where_values,
        ] = $this->build_where($where, true);
        
        // 使用 MySQL RAND() 随机排序
        $sql = "SELECT {$fields} FROM {$this->build_identifier($this->table_name)} {$where_sql}";
        
        return $this->wpdb->get_results($this->query_sql($sql, $where_values), ARRAY_A);
    }
    
    /**
     * 获取数据表中的行数。
     *
     * @param $where  array WHERE 条件
     * @param $column string 统计字段,默认 *
     * @return number 行的数量
     * */
    function count($where = [], $column = '*') {
        // COUNT 返回整数,空结果按 0 处理
        return (int)$this->aggregate('COUNT', $column, $where);
    }
    
    /**
     * 获取指定字段的最大值。
     *
     * @param $column string 统计字段
     * @param $where  array WHERE 条件
     * @return string|null 最大值
     */
    function max($column, $where = []) {
        // 调用通用聚合方法执行 MAX
        return $this->aggregate('MAX', $column, $where);
    }
    
    /**
     * 获取指定字段的最小值。
     *
     * @param $column string 统计字段
     * @param $where  array WHERE 条件
     * @return string|null 最小值
     */
    function min($column, $where = []) {
        // 调用通用聚合方法执行 MIN
        return $this->aggregate('MIN', $column, $where);
    }
    
    /**
     * 获取指定字段的平均值。
     *
     * @param $column string 统计字段
     * @param $where  array WHERE 条件
     * @return string|null 平均值
     */
    function avg($column, $where = []) {
        // 调用通用聚合方法执行 AVG
        return $this->aggregate('AVG', $column, $where);
    }
    
    /**
     * 获取指定字段的求和值。
     *
     * @param $column string 统计字段
     * @param $where  array WHERE 条件
     * @return string|null 求和值
     */
    function sum($column, $where = []) {
        // 调用通用聚合方法执行 SUM
        return $this->aggregate('SUM', $column, $where);
    }
    
    /**
     * 获取最后插入的 ID。
     *
     * @return number 最后插入的自增 ID
     */
    function id() {
        // $wpdb 会在 insert 后记录 insert_id
        return (int)($this->wpdb->insert_id ?? 0);
    }
    
    /**
     * 获取最后执行的 SQL。
     *
     * @return string 最后一次执行的 SQL
     */
    function last() {
        // 返回当前实例记录的最后一条 SQL
        return $this->last_sql;
    }
    
    /**
     * 返回所有执行的查询。
     *
     * @return array 当前实例执行过的 SQL 列表
     */
    function log() {
        // 返回 SQL 日志数组,方便调试
        return $this->logs;
    }
    
    /**
     * 执行聚合查询。
     *
     * @param $type   string 聚合函数名称
     * @param $column string 聚合字段
     * @param $where  array WHERE 条件
     * @return string|null 聚合结果
     */
    protected function aggregate($type, $column = '*', $where = []) {
        // 聚合函数只允许固定白名单
        $type = strtoupper($type);
        if (!in_array($type, [
            'COUNT',
            'MAX',
            'MIN',
            'AVG',
            'SUM',
        ], true)) return null;
        
        // 构建聚合字段和 WHERE 条件
        $field = $this->build_aggregate_column($column);
        if ($field === '') return null;
        
        [
            $where_sql,
            $where_values,
        ] = $this->build_where($where);
        
        // 执行聚合查询
        $sql = "SELECT {$type}({$field}) FROM {$this->build_identifier($this->table_name)} {$where_sql}";
        $result = $this->wpdb->get_var($this->query_sql($sql, $where_values));
        
        return $result === null ? null : (string)$result;
    }
    
    /**
     * 构建查询字段列表
     * 只允许普通字段名,避免字段名参与 SQL 注入;需要复杂表达式时建议子类单独实现。
     *
     * @param string|array $columns  返回字段
     * @return string
     */
    protected function build_columns($columns) {
        if ($columns === '*' || $columns === ['*']) return '*';
        
        // 字符串字段支持逗号分隔
        if (is_string($columns)) $columns = array_map('trim', explode(',', $columns));
        if (!is_array($columns)) return '*';
        
        // 过滤并转义字段名
        $fields = [];
        foreach ($columns as $column) {
            if (!$this->is_identifier($column)) continue;
            $fields[] = $this->build_identifier($column);
        }
        
        return empty($fields) ? '*' : implode(', ', $fields);
    }
    
    /**
     * 构建 WHERE 条件
     * 支持 Medoo 风格的字段运算符、AND/OR 嵌套和 ORDER/GROUP/HAVING/LIMIT。
     *
     * @param array $where         WHERE 条件
     * @param bool  $random_order  是否强制随机排序
     * @return array [SQL片段, prepare参数]
     */
    protected function build_where($where, $random_order = false) {
        if (empty($where) || !is_array($where)) return [
            $random_order ? 'ORDER BY RAND()' : '',
            [],
        ];
        
        $values = [];
        $where_parts = [];
        $conditions = [];
        
        // 分离普通条件和特殊子句
        foreach ($where as $field => $value) {
            if (in_array($field, [
                'GROUP',
                'HAVING',
                'ORDER',
                'LIMIT',
                'MATCH',
            ], true)) continue;
            $conditions[$field] = $value;
        }
        
        // 构建普通 WHERE 条件
        if (!empty($conditions)) {
            $condition_sql = $this->build_conditions($conditions, $values, 'AND');
            $where_parts[] = $condition_sql === '' ? '0=1' : $condition_sql;
        }
        
        // 构建 MATCH 全文检索条件
        if (isset($where['MATCH'])) {
            $match_sql = $this->build_match($where['MATCH'], $values);
            if ($match_sql !== '') $where_parts[] = $match_sql;
        }
        
        $clause = empty($where_parts) ? '' : 'WHERE ' . implode(' AND ', $where_parts);
        
        // 构建 GROUP BY
        if (isset($where['GROUP'])) {
            $group_sql = $this->build_group($where['GROUP']);
            if ($group_sql !== '') $clause .= ($clause === '' ? '' : ' ') . $group_sql;
        }
        
        // 构建 HAVING
        if (isset($where['HAVING'])) {
            $having_sql = $this->build_having($where['HAVING'], $values);
            if ($having_sql !== '') $clause .= ($clause === '' ? '' : ' ') . $having_sql;
        }
        
        // 构建 ORDER BY,随机查询会强制覆盖原 ORDER
        if ($random_order) {
            $clause .= ($clause === '' ? '' : ' ') . 'ORDER BY RAND()';
        } elseif (isset($where['ORDER'])) {
            $order_sql = $this->build_order($where['ORDER'], $values);
            if ($order_sql !== '') $clause .= ($clause === '' ? '' : ' ') . $order_sql;
        }
        
        // 构建 LIMIT
        if (isset($where['LIMIT'])) {
            $limit_sql = $this->build_limit($where['LIMIT']);
            if ($limit_sql !== '') $clause .= ($clause === '' ? '' : ' ') . $limit_sql;
        }
        
        return [
            $clause,
            $values,
        ];
    }
    
    /**
     * 判断写操作是否包含真实过滤条件。
     *
     * @param array $where  WHERE 条件
     * @return bool
     */
    protected function has_filter_condition($where) {
        if (empty($where) || !is_array($where)) return false;
        
        // MATCH 本身会生成 WHERE 条件
        if (isset($where['MATCH'])) return true;
        
        // 除特殊子句外,只要有普通条件就允许继续构建
        foreach ($where as $field => $value) {
            if (!in_array($field, [
                'GROUP',
                'HAVING',
                'ORDER',
                'LIMIT',
                'MATCH',
            ], true)) return true;
        }
        
        return false;
    }
    
    /**
     * 过滤写操作不支持的查询子句。
     *
     * @param array $where  WHERE 条件
     * @return array
     */
    protected function filter_write_where($where) {
        if (!is_array($where)) return [];
        
        // UPDATE / DELETE 不处理 GROUP 和 HAVING
        unset($where['GROUP'], $where['HAVING']);
        
        return $where;
    }
    
    /**
     * 构建普通条件片段
     * 支持 AND/OR 嵌套、字段运算符和字段对字段比较。
     *
     * @param array  $conditions  条件数组
     * @param array  $values      prepare 参数
     * @param string $connector   条件连接符
     * @return string
     */
    protected function build_conditions($conditions, &$values, $connector = 'AND') {
        $stack = [];
        
        foreach ($conditions as $key => $value) {
            // 处理 AND / OR 嵌套条件
            if (is_array($value) && preg_match('/^(AND|OR)(\s+#.*)?$/i', (string)$key, $relation_match)) {
                $relation = strtoupper($relation_match[1]);
                $nested = $this->build_conditions($value, $values, $relation);
                $stack[] = $nested === '' ? '(0=1)' : "({$nested})";
                continue;
            }
            
            // 处理字段和字段比较,如 ['created_at[>]updated_at']
            if (is_int($key)) {
                $field_compare = $this->build_field_compare($value);
                if ($field_compare !== '') $stack[] = $field_compare;
                continue;
            }
            
            // 处理普通字段条件
            [
                $field,
                $operator,
            ] = $this->parse_condition_key($key);
            if ($field === '') continue;
            
            $stack[] = $this->build_condition($field, $operator, $value, $values);
        }
        
        return implode(" {$connector} ", array_filter($stack));
    }
    
    /**
     * 构建单个字段条件
     * 根据字段运算符生成对应 SQL 片段。
     *
     * @param string $field     字段名
     * @param string $operator  运算符
     * @param mixed  $value     字段值
     * @param array  $values    prepare 参数
     * @return string
     */
    protected function build_condition($field, $operator, $value, &$values) {
        $field_sql = $this->build_identifier($field);
        $operator = strtolower(trim($operator ?: '='));
        
        // 处理等于、IS NULL 和 IN
        if ($operator === '=' || $operator === 'in') {
            if ($value === null) return "{$field_sql} IS NULL";
            if (is_array($value)) return $this->build_in($field_sql, $value, $values);
            
            return "{$field_sql} = " . $this->add_value($value, $values);
        }
        
        // 处理不等于、IS NOT NULL 和 NOT IN
        if ($operator === '!' || $operator === '!=' || $operator === 'not in') {
            if ($value === null) return "{$field_sql} IS NOT NULL";
            if (is_array($value)) return $this->build_in($field_sql, $value, $values, true);
            
            return "{$field_sql} != " . $this->add_value($value, $values);
        }
        
        // 处理大小比较
        if (in_array($operator, [
            '>',
            '>=',
            '<',
            '<=',
        ], true)) {
            return "{$field_sql} {$operator} " . $this->add_value($value, $values);
        }
        
        // 处理 LIKE 和 NOT LIKE
        if ($operator === '~' || $operator === '!~') {
            return $this->build_like($field_sql, $value, $values, $operator === '!~');
        }
        
        // 处理 BETWEEN 和 NOT BETWEEN
        if ($operator === '<>' || $operator === '><') {
            return $this->build_between($field_sql, $value, $values, $operator === '><');
        }
        
        // 处理 REGEXP
        if ($operator === 'regexp') {
            return "{$field_sql} REGEXP " . $this->add_value($value, $values);
        }
        
        return '0=1';
    }
    
    /**
     * 构建 IN / NOT IN 条件。
     *
     * @param string $field_sql  已转义字段名
     * @param array  $items      条件值
     * @param array  $values     prepare 参数
     * @param bool   $not        是否 NOT IN
     * @return string
     */
    protected function build_in($field_sql, $items, &$values, $not = false) {
        // 空 IN 必定无结果,空 NOT IN 不限制结果
        if (empty($items)) return $not ? '1=1' : '0=1';
        
        $placeholders = [];
        foreach ($items as $item) $placeholders[] = $this->add_value($item, $values);
        
        return $field_sql . ($not ? ' NOT' : '') . ' IN (' . implode(', ', $placeholders) . ')';
    }
    
    /**
     * 构建 LIKE / NOT LIKE 条件。
     *
     * @param string       $field_sql  已转义字段名
     * @param string|array $value      LIKE 条件
     * @param array        $values     prepare 参数
     * @param bool         $not        是否 NOT LIKE
     * @return string
     */
    protected function build_like($field_sql, $value, &$values, $not = false) {
        // LIKE 支持字符串或数组,数组默认使用 OR 连接
        if (!is_array($value)) $value = [$value];
        
        $connector = 'OR';
        if (isset($value['AND']) && is_array($value['AND'])) {
            $connector = 'AND';
            $value = $value['AND'];
        } elseif (isset($value['OR']) && is_array($value['OR'])) {
            $connector = 'OR';
            $value = $value['OR'];
        }
        
        $likes = [];
        foreach ($value as $item) {
            $likes[] = $field_sql . ($not ? ' NOT' : '') . ' LIKE ' . $this->add_value($this->build_like_value($item), $values);
        }
        
        return empty($likes) ? '0=1' : '(' . implode(" {$connector} ", $likes) . ')';
    }
    
    /**
     * 构建 BETWEEN / NOT BETWEEN 条件。
     *
     * @param string $field_sql  已转义字段名
     * @param array  $value      范围值
     * @param array  $values     prepare 参数
     * @param bool   $not        是否 NOT BETWEEN
     * @return string
     */
    protected function build_between($field_sql, $value, &$values, $not = false) {
        // BETWEEN 必须传入两个范围值
        if (!is_array($value) || count($value) < 2) return '0=1';
        
        $left = $this->add_value($value[0], $values);
        $right = $this->add_value($value[1], $values);
        
        return "({$field_sql}" . ($not ? ' NOT' : '') . " BETWEEN {$left} AND {$right})";
    }
    
    /**
     * 构建字段和字段比较条件。
     *
     * @param string $condition  条件字符串
     * @return string
     */
    protected function build_field_compare($condition) {
        if (!is_string($condition)) return '';
        
        // 匹配 left[operator]right 格式
        if (!preg_match('/^([^\[\]]+)\[([^\]]+)\]([^\[\]]+)$/u', $condition, $match)) return '';
        
        $left = trim($match[1]);
        $operator = trim($match[2]);
        $right = trim($match[3]);
        
        if (!$this->is_identifier($left) || !$this->is_identifier($right)) return '';
        if ($operator === '!') $operator = '!=';
        if (!in_array($operator, [
            '>',
            '>=',
            '<',
            '<=',
            '=',
            '!=',
        ], true)) return '';
        
        return $this->build_identifier($left) . " {$operator} " . $this->build_identifier($right);
    }
    
    /**
     * 构建 MATCH 全文检索条件。
     *
     * @param array $match   MATCH 条件
     * @param array $values  prepare 参数
     * @return string
     */
    protected function build_match($match, &$values) {
        if (!is_array($match) || empty($match['columns']) || !isset($match['keyword'])) return '';
        
        // 过滤全文检索字段
        $columns = [];
        foreach ((array)$match['columns'] as $column) {
            if ($this->is_identifier($column)) $columns[] = $this->build_identifier($column);
        }
        
        if (empty($columns)) return '';
        
        // 处理全文检索模式
        $mode = '';
        $options = [
            'natural' => ' IN NATURAL LANGUAGE MODE',
            'natural+query' => ' IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION',
            'boolean' => ' IN BOOLEAN MODE',
            'query' => ' WITH QUERY EXPANSION',
        ];
        if (!empty($match['mode']) && isset($options[$match['mode']])) $mode = $options[$match['mode']];
        
        return 'MATCH (' . implode(', ', $columns) . ') AGAINST (' . $this->add_value($match['keyword'], $values) . $mode . ')';
    }
    
    /**
     * 构建 GROUP BY 子句。
     *
     * @param string|array $group  分组字段
     * @return string
     */
    protected function build_group($group) {
        $groups = is_array($group) ? $group : [$group];
        $stack = [];
        
        // 过滤并转义分组字段
        foreach ($groups as $column) {
            if ($this->is_identifier($column)) $stack[] = $this->build_identifier($column);
        }
        
        return empty($stack) ? '' : 'GROUP BY ' . implode(', ', $stack);
    }
    
    /**
     * 构建 HAVING 子句。
     *
     * @param array $having  HAVING 条件
     * @param array $values  prepare 参数
     * @return string
     */
    protected function build_having($having, &$values) {
        if (empty($having) || !is_array($having)) return '';
        
        // HAVING 复用普通条件语法
        $condition_sql = $this->build_conditions($having, $values, 'AND');
        
        return $condition_sql === '' ? '' : 'HAVING ' . $condition_sql;
    }
    
    /**
     * 构建 ORDER BY 子句。
     *
     * @param string|array $order   排序配置
     * @param array        $values  prepare 参数
     * @return string
     */
    protected function build_order($order, &$values) {
        // 字符串排序只支持单个字段名
        if (is_string($order)) {
            return $this->is_identifier($order) ? 'ORDER BY ' . $this->build_identifier($order) : '';
        }
        
        if (!is_array($order)) return '';
        
        $stack = [];
        foreach ($order as $column => $value) {
            // 数字索引写法:['id','name']
            if (is_int($column)) {
                if ($this->is_identifier($value)) $stack[] = $this->build_identifier($value);
                continue;
            }
            
            if (!$this->is_identifier($column)) continue;
            
            // 数组写法会转成 FIELD(column, ...)
            if (is_array($value)) {
                $field_values = [];
                foreach ($value as $item) $field_values[] = $this->add_value($item, $values);
                if (!empty($field_values)) $stack[] = 'FIELD(' . $this->build_identifier($column) . ', ' . implode(', ', $field_values) . ')';
                continue;
            }
            
            // ASC / DESC 排序
            $direction = strtoupper((string)$value);
            if (in_array($direction, [
                'ASC',
                'DESC',
            ], true)) {
                $stack[] = $this->build_identifier($column) . ' ' . $direction;
            }
        }
        
        return empty($stack) ? '' : 'ORDER BY ' . implode(', ', $stack);
    }
    
    /**
     * 构建 LIMIT 子句。
     *
     * @param number|array $limit  数量或 [offset, limit]
     * @return string
     */
    protected function build_limit($limit) {
        // LIMIT 数字写法
        if (is_numeric($limit)) {
            return 'LIMIT ' . max(0, (int)$limit);
        }
        
        // LIMIT 数组写法:[offset, limit]
        if (is_array($limit) && isset($limit[0], $limit[1]) && is_numeric($limit[0]) && is_numeric($limit[1])) {
            $offset = max(0, (int)$limit[0]);
            $count = max(0, (int)$limit[1]);
            
            return "LIMIT {$count} OFFSET {$offset}";
        }
        
        return '';
    }
    
    /**
     * 解析字段条件 key。
     *
     * @param string $key  字段条件 key
     * @return array [字段名, 运算符]
     */
    protected function parse_condition_key($key) {
        if (!is_string($key)) return [
            '',
            '',
        ];
        
        // 匹配 field[operator] 格式
        if (!preg_match('/^([^\[\]]+)(?:\[([^\]]*)\])?$/u', $key, $match)) return [
            '',
            '',
        ];
        
        $field = trim($match[1]);
        $operator = isset($match[2]) && $match[2] !== '' ? trim($match[2]) : '=';
        
        if (!$this->is_identifier($field)) return [
            '',
            '',
        ];
        
        return [
            $field,
            $operator,
        ];
    }
    
    /**
     * 构建聚合字段。
     *
     * @param string $column  聚合字段
     * @return string
     */
    protected function build_aggregate_column($column) {
        if ($column === '*' || $column === null || $column === '') return '*';
        
        return $this->is_identifier($column) ? $this->build_identifier($column) : '';
    }
    
    /**
     * 构建 LIKE 参数值。
     *
     * @param mixed $value  原始值
     * @return string
     */
    protected function build_like_value($value) {
        $value = (string)$value;
        
        // 已经手动传入 % 或 _ 时,认为调用方正在控制 LIKE 模式
        if (strpos($value, '%') !== false || strpos($value, '_') !== false) return $value;
        
        $value = method_exists($this->wpdb, 'esc_like') ? $this->wpdb->esc_like($value) : addcslashes($value, '_%\\');
        
        return '%' . $value . '%';
    }
    
    // 添加一个 prepare 参数并返回占位符
    protected function add_value($value, &$values) {
        $values[] = $value;
        
        return '%s';
    }
    
    // 判断字段名或表名是否为普通 SQL 标识符
    protected function is_identifier($name) {
        if (!is_string($name) || $name === '') return false;
        
        $parts = explode('.', $name);
        foreach ($parts as $part) {
            if (!preg_match('/^[\p{L}_][\p{L}\p{N}@$#\-_]*$/u', $part)) return false;
        }
        
        return true;
    }
    
    // 给字段名或表名加反引号,避免和 SQL 保留字冲突
    protected function build_identifier($name) {
        $parts = explode('.', $name);
        $parts = array_map(function ($part) {
            return '`' . str_replace('`', '``', $part) . '`';
        }, $parts);
        
        return implode('.', $parts);
    }
    
    // 准备并记录 SQL,所有直接执行的 SQL 都走这里
    protected function query_sql($sql, $values = []) {
        $sql = $this->prepare_sql($sql, $values);
        $this->record_sql($sql);
        
        return $sql;
    }
    
    // 统一处理 prepare 参数,兼容没有参数的 SQL
    protected function prepare_sql($sql, $values = []) {
        if (empty($values)) return $sql;
        
        return call_user_func_array([
            $this->wpdb,
            'prepare',
        ], array_merge([$sql], $values));
    }
    
    // 记录最后一次 SQL 和全部 SQL 日志
    protected function record_sql($sql) {
        if (empty($sql)) return;
        
        $this->last_sql = $sql;
        $this->logs[] = $sql;
    }
    
}