---
id: FEAT-010
title: where-dsl
domain: query
status: done
since_version: 0.2.0
---

# FEAT-010 — 受限 WHERE DSL 查匹配

## User Story

As a **登录用户（的 agent）**, I want to **用类 SQL 的 WHERE 表达式筛选目标**, so that **不被平台算法推荐绑架，自己决定查什么人**。

## Acceptance Criteria

- [x] `xq query '<WHERE>' [--limit N] [--json]` 返回匹配用户列表
- [x] 字段白名单：`gender / age / city / height / education / tags`（不允许查未列入字段）
- [x] 操作符白名单：`= != > >= < <= IN CONTAINS AND`（禁 `OR / JOIN / 子查询`）
- [x] 值白名单：`age 18-99` / `height 140-220` / `education` 枚举 / `gender` 枚举
- [x] 服务端**零排序算法**（`ORDER BY updated_at DESC`，不做推荐不做付费置顶）
- [x] 默认 `--limit 50`，最大 100（防爬虫全拉）
- [x] 结果**不含**查询者自己（过滤 session user_id）
- [x] 拉黑过的 / 被拉黑的**不出现**在结果里
- [x] DSL 语法错误 → 400 `query failed: ...`

## Tests

- `test/test_query.py::test_gender_age_city_combo`
- `test/test_query.py::test_tags_contains`
- `test/test_query.py::test_limit_boundary`（1 / 50 / 100 / 101）
- `test/test_query.py::test_whitelist_reject_or`
- `test/test_query.py::test_whitelist_reject_unknown_field`
- `test/test_query.py::test_filter_self_and_blocked`
- `test/test_dsl.py::test_parser_happy_paths`（~15 条合法语法）
- `test/test_dsl.py::test_parser_reject_invalid`（~10 条非法语法）

## CLI / API

- CLI: `xq query '<WHERE>' [--limit N] [--json]`（实现：`src/xiangqin/cli.py::cmd_query`）
- API: `POST /query`（实现：`src/xiangqin/app.py::query` → `src/xiangqin/query.py::run` → `src/xiangqin/dsl.py::parse`）

## 依赖 / 被依赖

- **依赖**：`FEAT-001/002` 登录、`FEAT-022..024 block` 过滤
- **被依赖**：`FEAT-014 dm.send`（通过 query 找 user_id 发信）

## 风险 / 边界

- **产品红线**：服务端**绝对**不做推荐 / 排序算法。查询权永远在用户 agent 手里。这是和 Tinder / Soul 等相亲社交产品的根本差异
- **性能**：sqlite + 索引撑 10k 用户 1 QPS 没问题，超此量级转 Postgres
- **防爬**：`--limit` 上限 + 业务层限流（`#019`）

## 相关

- docs/design.md#受限-where-dsl
- ADR：查询权归用户（`2026-04-22-1800-commence-xiangqin`）
