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

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法