项目概述
本项目在关系型数据库之上实现全文检索 API:以加权向量列让标题权重高于正文,用索引加速匹配,返回带高亮的片段,并为输入提示提供前缀匹配。要点是把检索交给索引与数据库内建排序,而不是在应用里重写一遍。
架构与关键决策
生成的向量列由源列自动合成,因此不会与内容失配,更新文档即更新向量;覆盖该列的索引使包含查询走索引而非全表扫描。查询路径把用户输入解析为词项查询,按内建排序函数排名,并返回高亮片段,高亮留在数据库可避免用第二个解析器重述查询。语言配置一旦选定即同时进入列与查询,改动即 schema 变更。
实施与测试
以固定语料断言排序,且语料中标题命中应高于正文命中;仅含停用词的查询断言无结果;断言高亮包裹命中词、前缀路径可命中部分词;断言词项匹配的查询计划确实使用索引。
限制与后续
- 单语言配置,真正的多语言语料需按语言分列。
- 除静态权重外无相关性调优。
- 无模糊匹配与同义词,拼写纠错需额外索引。
代码示例
pnpm init
pnpm add pg express
pnpm add -D typescript tsx vitest
full-text-search-api/
├── src/search.ts
├── src/index-doc.ts
├── src/ranking.ts
├── db/schema.sql
├── tests/search.test.ts
└── package.json
ALTER TABLE docs ADD COLUMN search tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title,'')), 'A') ||
setweight(to_tsvector('english', coalesce(body,'')), 'B')) STORED;
CREATE INDEX docs_search_idx ON docs USING GIN (search);
SELECT id, title, ts_rank_cd(search, query) AS rank
FROM docs, websearch_to_tsquery('english', $1) query
WHERE search @@ query ORDER BY rank DESC LIMIT 20;
ts_headline('english', body, query, 'MaxFragments=2, StartSel=<mark>, StopSel=</mark>') AS snippet
const q = tokens.slice(0, -1).join(' & ') + ' & ' + last + ':*';
pnpm vitest run