Regex queries are not allowed.
主线位置:查询主线第二步。语法层认识
field:/pattern/,交给QueryParser展开时先看有没有打开正则开关。正则查询怎么在词典的 FST 上跑见词典,之后读倒排表、打常数分见短语与其他查询。
flowchart TB
in["body:/gre.*/"] --> leaf["Regex 叶子"]
leaf --> sw{"regexes_allowed"}
sw -->|"false 默认"| err["UnsupportedQuery"]
sw -->|true| f{"写了字段"}
f -->|没有| err2["需要指定字段"]
f -->|有| rq["RegexQuery"]
rq --> fst["在词典上整词匹配"]
实验在 exp-tantivy/pitb-lab,bin p20_regex(release):wiki.json 1000 篇,默认字段 title、body(都是 TEXT),一个段。M2 Max、macOS 27、rustc 1.99.0,tantivy 0.27.0。
现象
default: Err(UnsupportedQuery("Regex queries are not allowed."))
default: Unsupported query: Regex queries are not allowed.
lenient: 9 docs errors=[UnsupportedQuery("Regex queries are not allowed.")]
第一行是 parse_query("body:/gre.*/") 的 Debug,第二行是它的 Display。容错解析 britain body:/gre.*/ 不失败:正则那一项被扔掉,只按 britain 查出 9 篇,错误只出现在返回的列表里。
原因
QueryParser 有一个 regexes_allowed 字段,构造时是 false。展开到正则叶子时,第一件事就是检查它:
UserInputLeaf::Regex { field, pattern } => {
if !self.regexes_allowed {
return (
None,
vec![QueryParserError::UnsupportedQuery(
"Regex queries are not allowed.".to_string(),
)],
);
}
返回 None 加一条错误:严格解析取第一条错误返回,容错解析把这个叶子当空子句删掉。默认关掉的理由很直接:正则查询没有展开上限,匹配多少词就读多少条倒排表(短语与其他查询),把它交给搜索框里的任意用户,等于让一个 /.*/ 读遍整个字段的倒排表。
0.26.0 之前,查询语法里没有正则:0.25.0 的 query_grammar.rs 里找不到 regex 字样,/ 也不在特殊字符表里,按那时的源码,body:/gre.*/ 只是 body 字段上的一个普通的词,交给分词器后剩下 gre。#2677(f4b374110,「feat: Regex query grammar」,首次发布于 0.26.0)给语法层加了 Regex 叶子、给解析器加了 LogicalLiteral::Regex,提交说明里的第二条就是「feat: Disable regexes by default」:同时加上 regexes_allowed: false 和 allow_regexes()。CHANGELOG 0.26 记为「Add support for regexes in the query grammar」。所以从 0.25 升上来的服务,用户输入里的 /.../ 从「被当成普通词」变成了报错。
修法
确定要给用户正则能力时,显式打开:
let mut qp = QueryParser::for_index(&index, vec![title, body]);
qp.allow_regexes();
打开以后,下面几条结果最容易让人以为正则没生效:
body:/gre.*/ => 112 docs RegexQuery { regex: Regex
body:/Gre.*/ => 0 docs RegexQuery { regex: Regex
body:/gre/ => 0 docs RegexQuery { regex: Regex
body:/great/ => 35 docs RegexQuery { regex: Regex
body:/great britain/ => 0 docs RegexQuery { regex: Regex
/gre.*/ => Err: Unsupported query: Regex query need to target a specific field.
body:/gre[/ => Err: Unsupported query: Invalid regex: regex parse error:
gre[
^
error: unclosed character class
(RegexQuery 的 Debug 会打印整个自动机,这里只留了类型名。)
- 匹配的是索引里的词,不是原文。
default分词器把词都小写了,/Gre.*/一个也对不上。 - 整词匹配。 正则要匹配整个词,
/gre/不是「包含 gre」,只匹配恰好是gre的词;要包含就写/.*gre.*/;开头是.*的正则对任何前缀都还可能匹配,自动机在 FST 上剪不了枝,要走遍整个词典。 - 不能跨词。 词典里没有
great britain这样带空格的词,跨词要用RegexPhraseQuery。 - 必须写字段名,不会展开到默认字段。
- 正则写错是解析错误,带着
regexcrate 的原始报错,多行。
怎么检查
- 用了
parse_query_lenient的地方,把错误列表记进日志;UnsupportedQuery("Regex queries are not allowed.")出现在那里时,用户的查询少了一项。 - 打开了
allow_regexes的服务,给查询加超时或者只对受信任的调用方开放;在测试里跑一次/.*/,看看耗时。 - 写测试时用索引里的形式(小写、整词)构造期望,别拿原文去比。
小结
QueryParser默认不允许正则:严格解析报UnsupportedQuery("Regex queries are not allowed."),容错解析悄悄丢掉那一项。这是 0.26.0 加入正则语法时(#2677)一并定的默认值。allow_regexes()打开后,正则按整词匹配索引里的词(小写过的),不跨词,必须写字段,没有展开上限。