# 让符号、中文和版本号一起被搜到

> 符号、两个字的中文词和版本前缀分别怎样处理，以及我们用什么来检验。

---

LLMS 索引： [llms.txt](/llms.txt)

---

PostgreSQL 用户搜索的东西很杂：参数名 `work_mem`，运算符 `->>`，psql 命令 `\dC`，错误码 `23505`，
中文词“复制延迟”，还常常带着版本号。通用的全文检索对这些都不友好。这篇说明 Crib 的做法。

## 先查名字，再查正文 {#tiers}

排序分四层，前一层的结果总排在后一层前面：

1. 当前范围内的精确名称和别名；
2. 名称前缀；
3. 标题和章节名；
4. 正文。

精确名称存在一张普通的 B-tree 表里，键按类型规范化：配置参数、函数、运算符、psql 命令、SQLSTATE 各有自己的规则。
`\dC` 和 `\dc` 是两个不同的命令，大小写保留；`->>` 和 `@@` 按原样保存，不会被当成标点丢掉；
SQLSTATE 代码按字符串处理，前导零不丢。同名的函数重载各自保留，不会因为名字相同只剩一个。

这张表由构建端从手册源码里提取，比如参数定义列表中的 `max_connections` 记为“定义”，正文里提到它的地方只算“提及”。
所以搜 `max_connections` 时，第一条是参数定义本身，而不是某段恰好提到它的正文。

粘贴进来的对象名也能认出来：`pg_catalog.max_connections;` 会命中 `max_connections` 的定义，并在结果和正文里高亮这个名字。
运算符、psql 命令和 SQL 表达式则保持原样，不做这种改写。

## 中文按双字切分 {#han}

SQLite 自带的 `unicode61` 分词器会把一整段中文当成一个词；`trigram` 分词器不匹配少于三个字符的查询，
“事务”“索引”这样两个字的词就搜不到。

Crib 用自己的切分规则：连续汉字按重叠双字切分，“事务隔离”切成“事务、务隔、隔离”；拉丁字母的词和标识符另外提取。
切分结果编码成带类型前缀的 ASCII 词项再交给 FTS5，构建端（Go）和应用（Swift）用同一套规则，并用同一组测试样例核对。
规则有自己的版本号，写在每个资料包的清单里，规则一改，旧包和新包不会混用。

双字切分只能证明两个字出现过，不能证明短语成立。所以加了引号的短语还会回到原文核对顺序和连续性。

单个汉字没有双字可切。“锁”这样的查询走标题和名称的字符索引，在有限的候选里查，并说明范围，不做全库扫描。

输入法全角模式打出的 `ｖａｃｕｕｍ` 会在查询端折成半角再搜；全角逗号、括号是中文正文的一部分，保持原样。版本前缀和筛选条件里的全角冒号（`pg18：`）也能识别。

## 版本前缀和过滤器 {#filters}

查询先解析成结构化条件。`PG96:`、`pg@18.6:`、`pgdevel:` 是版本前缀，`lang:`、`kind:`、`branch:`、`collection:`、
`domain:`、`project:` 是过滤器，名单是固定的，其余都当作文本。`pg_stat_activity:` 或者粘贴进来的 `ERROR:` 不是版本前缀，
照常当文本搜。

别名由已签名的版本目录解析：`pg18` 指向 18.6，`pg96` 指向 9.6.24。目标版本没有安装或被停用时，搜索直接说明，
不会悄悄换成同一条线的其他小版本。显式写了 `lang:en`，就只返回英文正文。

## 多个版本折叠成一组 {#groups}

`work_mem` 在 31 条版本线里都有。如果每个版本各占一行，第一页就全是它。Crib 只在已经证明是同一个对象时才折叠：
同一组里，当前选中的版本排在前面，其他版本展开后可见。出现在 31 个版本里不会让它的相关性变高；
没有证明是同一对象的同名条目，各自独立。

## 用什么来检验 {#evaluation}

我们维护了一组 369 条标注过的查询，覆盖精确名称、符号、psql 命令、SQLSTATE、中文、中英混合和版本过滤。
每条的期望结果由独立的审核者对照固定来源确认。2026-11-02 在当前源码和索引上的完整运行结果：

| 指标 | 结果 |
| --- | --- |
| 前 10 条内找到期望结果 | 369 / 369 |
| 要求排第一的查询 | 270 / 270 |
| 过滤、分组、摘要等约束违例 | 0 |
| 单版本搜索，热缓存 p95 | 31.3 ms |
| 全部版本搜索，热缓存 p95 | 21.8 ms |

延迟是在开发机（Apple M5 Max）上测的，不代表其他机器。这组查询会随着问题报告继续扩充；
它证明的是这些查询的行为，不是所有查询都同样好。

搜索语法的完整写法见[搜索](/docs/guide/search/)。

---

反链：

- [为什么做 Crib](/blog/post/why-crib/)
