下面把四类“复杂查询”在 Lucene/Elasticsearch 中的**重写(rewrite)流程**拆开说:
核心思路都是“**先把索引里真正命中的底层 Term 或点集合找出来,再组合成可快速倒排扫描的原子查询**”。
-------------------------------------------------
1. WildcardQuery
-------------------------------------------------
原始查询
```
WildcardQuery("title", "jav*")
```
重写步骤
1. 读取 title 字段的 **TermsEnum**(已按字典序排序)。
2. 用 **Automaton**(通配符自动机)在字典里 **顺序遍历** 所有以 `jav` 开头的真实词项,例如:`java`, `javascript`, `javaw`。
3. 把这些词项封装成一个 **BooleanQuery**(默认 SHOULD):
```
BooleanQuery
├── TermQuery(title:java)
├── TermQuery(title:javascript)
└── TermQuery(title:javaw)
```
4. 如果命中的 Term 太多(默认 ≥1024,受 `indices.query.bool.max_clause_count` 限制),会改写成 **MultiTermQueryConstantScoreWrapper**(常量分数 + 位图合并),避免 BooleanQuery 爆炸。
-------------------------------------------------
2. RangeQuery(numeric/date/ip)
-------------------------------------------------
原始查询
```
RangeQuery("price", [50, 100)
```
重写步骤
1. 判断字段类型:
• keyword/text → 退化成 **TermRangeQuery**,过程同 Wildcard 的顺序遍历。
• numeric/date/ip → 走 **PointRangeQuery**(BKD 树)。
2. 在 BKD 树中 **点范围查询**,一次性拿到所有落在 `[50, 100)` 的 **docId 位图**。
3. 重写成 **PointRangeQuery**(内部直接返回位图),不再拆 Term,速度 O(log N)。
-------------------------------------------------
3. Geo 查询
-------------------------------------------------
以 `GeoDistanceQuery` 为例:
1. 计算中心点 + 半径 → 得到 **覆盖矩形**。
2. 在 BKD 树(geo_point 字段)中 **行/列扫描** 拿到矩形内的所有点。
3. 再精确 **Haversine 公式** 过滤掉矩形四角外的点。
4. 最终重写成 **LatLonPointDistanceQuery**(位图),无 Term 级拆分。
-------------------------------------------------
4. ScriptQuery
-------------------------------------------------
原始查询
```
ScriptQuery("doc['price'].value * doc['discount'].value > 40")
```
重写步骤
1. **无法拆成 Term**,因为逻辑是任意代码。
2. 重写阶段 **原样保留**(`ScriptQuery` 本身已是原子查询)。
3. 执行时变成真正的 **逐文档脚本计算**(最昂贵,无法走倒排索引)。
4. 如果脚本里引用了 `_score`,还会强制收集原始分数;否则可跳过。
-------------------------------------------------
一句话速记
- Wildcard/Prefix → 拆 **Term 列表** → BooleanQuery
- Range/Geo → 直接走 **BKD 树** → 位图
- Script → **不拆分**,执行期逐条算
重写结束后,QueryPhase 拿到全是可位图/跳表直接算交集的“原子查询”,才开始真正的倒排扫描。