update
This commit is contained in:
58
lib/search-plan.js
Normal file
58
lib/search-plan.js
Normal file
@@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
// trigram 分词器按 3 字符滑窗建索引,因此**任何**短于 3 字符的词都匹配不到。
|
||||
const TRIGRAM_MIN = 3;
|
||||
|
||||
/**
|
||||
* 转义 LIKE 元字符(% _ \),供 `LIKE ? ESCAPE '\'` 使用。
|
||||
* 用户输入里的 % / _ 不转义会被当通配符:搜 "100%" 变成匹配一切以 100 开头的串。
|
||||
*/
|
||||
function escapeLike(s) {
|
||||
return String(s).replace(/[\\%_]/g, (c) => '\\' + c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把查询拆成「每个 token 一个 LIKE 模式」的数组(已转义)。
|
||||
* 多 token 是 AND 关系 —— 与 FTS MATCH 多短语的语义对齐,
|
||||
* 且各 token 独立匹配,不要求原文里连空格一起逐字出现。
|
||||
*
|
||||
* 空查询返回空数组(调用方按"列出全部"处理)。
|
||||
*/
|
||||
function likePatterns(query) {
|
||||
const trimmed = String(query == null ? '' : query).trim();
|
||||
if (!trimmed) return [];
|
||||
return trimmed.split(/\s+/).filter(Boolean).map((tok) => `%${escapeLike(tok)}%`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 决定一个搜索词该走 FTS 还是退回 LIKE,并给出安全转义后的 FTS 查询串。
|
||||
*
|
||||
* 背景:clips_fts 用的是 trigram 分词器(见 lib/migrate.js 的 v2->v3)。
|
||||
* 选它是因为默认的 unicode61 会把一整串中文当成一个 token,导致
|
||||
* "公园" 搜不到 "今天去公园散步" —— 中文场景下搜索基本全废。
|
||||
* trigram 解决了子串匹配,但代价是 ≤2 字符的词一条也匹配不到,
|
||||
* 所以这类查询必须退回 LIKE("公园""会议""复制" 这种两字词非常常见)。
|
||||
*
|
||||
* 另外,用户输入直接塞进 MATCH 会因 FTS5 保留字符(- " ( ) * ^ :)报语法错误,
|
||||
* 所以每个 token 都要包成 FTS5 字符串字面量。
|
||||
*
|
||||
* @param {string} query 用户原始输入
|
||||
* @returns {{mode:'all'} | {mode:'like', patterns:string[]} | {mode:'fts', match:string}}
|
||||
*/
|
||||
function planSearch(query) {
|
||||
const patterns = likePatterns(query);
|
||||
if (patterns.length === 0) return { mode: 'all' };
|
||||
|
||||
// 任何一个 token 太短 → 整个 MATCH 都会返回空(多 token 是 AND 关系),
|
||||
// 所以只要有一个短 token 就整体退回 LIKE。
|
||||
// 注意用 [...tok] 按码点算长度,别用 .length(代理对会算成 2)。
|
||||
const trimmed = String(query == null ? '' : query).trim();
|
||||
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
||||
const tooShort = tokens.some((tok) => [...tok].length < TRIGRAM_MIN);
|
||||
if (tooShort) return { mode: 'like', patterns };
|
||||
|
||||
const match = tokens.map((tok) => `"${tok.replace(/"/g, '""')}"`).join(' ');
|
||||
return { mode: 'fts', match };
|
||||
}
|
||||
|
||||
module.exports = { planSearch, likePatterns, escapeLike, TRIGRAM_MIN };
|
||||
Reference in New Issue
Block a user