StackTraces
Meilisearch已验证

Meilisearch filter 组合缺括号,AND 优先级吞掉 OR 约束

@debug_master7 天前更新阅读 6 min00

filter 字符串表达式里 AND 优先级高于 OR:A OR B AND C 会被解析成 A OR (B AND C)。跨条件组合时漏掉括号,结果与预期不符且不报任何错误(HTTP 200)。组合条件必须显式加括号:(A OR B) AND C。官方规范 BNF 明确 or = and ("OR" and)*,AND 嵌套在 OR 之内,因此 AND 先结合。适用 Meilisearch 全版本。

Issue & Env(案发现场与环境)

  • 业务场景:想表达“类型是恐怖片或喜剧片,并且上映时间晚于 1995-03-25(Unix 时间戳 795484800)”,把 filter 写成 genres = horror OR genres = comedy AND release_date > 795484800,结果返回了全部恐怖片(包含 1995 年前的老片),只有喜剧片分支正确地应用了日期限制。
  • 环境:Meilisearch v1.54.3,curl / 任意 SDK 均可复现。
  • 关键区分:这不是语法错误——表达式完全合法,请求返回 200,estimatedTotalHits 也正常。这是操作符优先级导致的语义错误,属于最隐蔽的一类坑:没有报错,只有结果不对。

StackTrace(堆栈日志)

官方 Getting started / filter expression reference 指南给出的括号强制示例原文:

(genres = horror OR genres = comedy) AND release_date > 795484800

官方明确警告:去掉括号后,该表达式会被解析为

genres = horror OR (genres = comedy AND release_date > 795484800)

观察到的现象:无任何报错(HTTP 200),但结果集与预期不同——所有 horror 电影都进来了(不再受日期约束),只有 comedy 分支受日期限制。

官方规范(specs 0118-search-api)中 filter 语法的 BNF 原文:

filter = expression EOF;
expression = or;
or = and ("OR" WS+ and)*;
and = not ("AND" WS+ not)*;
not = ("NOT" WS+ not) | primary;
primary = "(" WS* expression WS* ")" | ...

从文法结构看,or 的每个操作数是一个完整的 and 表达式:AND 先结合,OR 后结合。所以 A OR B AND C 等价于 A OR (B AND C),OR 组永远“逃逸”不出 AND 的约束。

Reproduce(复现代码)

# 目标:恐怖片或喜剧片,且 release_date > 795484800(约 1995-03-25)

# 错误写法——缺括号
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer aSampleMasterKey' \
  --data-binary '{"q": "", "filter": "genres = horror OR genres = comedy AND release_date > 795484800"}'
# 结果:所有 horror 电影(含 1995 年前的老片)+ 1995 年后的 comedy

# 正确写法——跨条件组合加括号
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer aSampleMasterKey' \
  --data-binary '{"q": "", "filter": "(genres = horror OR genres = comedy) AND release_date > 795484800"}'
# 结果:仅 1995 年后的 horror 或 comedy

Root Cause(根因分析)

AND 优先于 OR 是官方明确的设计

Meilisearch 的 filter 表达式解析采用与 SQL 一致的优先级约定:AND 高于 OR,括号用于覆盖默认结合顺序。官方规范 BNF 从文法层面固定了这一点(or = and ("OR" and)*),不是实现 bug。

前端动态拼 filter 字符串是最高频触发场景

分面导航的典型拼法:同一分面内多选值用 OR 连接(如 genres = horror OR genres = comedy),跨分面用 AND 连接(如 ... AND language = English)。一旦把两组条件直接拼接成 genres = horror OR genres = comedy AND language = English,OR 组就会脱离 AND 约束,喜剧片 + 语言条件的组合被“吞掉”。

排查方法

  • 把 filter 字符串打印出来,按“AND 先结合”的规则人工还原解析树,对比预期。
  • 或改用数组形式重写:外层数组元素间隐含 AND、内层数组元素间隐含 OR,层级一目了然,不用手写括号(见下)。

The Fix(解决方案)

规则:任何“多个 OR 组合再与其他条件 AND”的场景,OR 组必须整体加括号

curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer aSampleMasterKey' \
  --data-binary '{"q": "", "filter": "(genres = horror OR genres = comedy) AND release_date > 795484800"}'

等价数组写法(避免手写括号)

curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer aSampleMasterKey' \
  --data-binary '{
    "q": "",
    "filter": [["genres = horror", "genres = comedy"], "release_date > 795484800"]
  }'

数组形式的语义:外层数组元素之间用 AND 连接,内层数组元素之间用 OR 连接,可内外混用;嵌套最大深度为 2,三层以上直接抛错。结构显式化之后,优先级不会再产生歧义。

注意事项

  • NOT 同样受括号影响:release_date > 1577884550 AND (NOT director = "Tim Burton") 中的括号也是强制的,去掉后 NOT 只作用于后一个条件。
  • 复杂表达式建议先对照官方 filter expression reference 核对优先级,再上线。
  • 验证技巧:先不加括号请求一次,再加括号请求一次,对比两次 estimatedTotalHits,差异一眼可见。

References(参考来源)

最后更新于 2026-10-04

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法