mongo-query-normalizer 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +109 -441
  2. package/README.zh-CN.md +109 -430
  3. package/dist/compile/compile.js +13 -0
  4. package/dist/compile/compile.js.map +1 -1
  5. package/dist/normalize.d.ts.map +1 -1
  6. package/dist/normalize.js +3 -0
  7. package/dist/normalize.js.map +1 -1
  8. package/dist/passes/normalize-predicate.d.ts.map +1 -1
  9. package/dist/passes/normalize-predicate.js +38 -4
  10. package/dist/passes/normalize-predicate.js.map +1 -1
  11. package/dist/predicate/capabilities/eq/eq-eq.js +9 -11
  12. package/dist/predicate/capabilities/eq/eq-eq.js.map +1 -1
  13. package/dist/predicate/capabilities/eq/eq-in.d.ts.map +1 -1
  14. package/dist/predicate/capabilities/eq/eq-in.js +9 -50
  15. package/dist/predicate/capabilities/eq/eq-in.js.map +1 -1
  16. package/dist/predicate/capabilities/eq/eq-range.d.ts.map +1 -1
  17. package/dist/predicate/capabilities/eq/eq-range.js +11 -80
  18. package/dist/predicate/capabilities/eq/eq-range.js.map +1 -1
  19. package/dist/predicate/capabilities/range/range-range.d.ts.map +1 -1
  20. package/dist/predicate/capabilities/range/range-range.js +1 -10
  21. package/dist/predicate/capabilities/range/range-range.js.map +1 -1
  22. package/dist/predicate/planner/relation-planner.d.ts.map +1 -1
  23. package/dist/predicate/planner/relation-planner.js +0 -7
  24. package/dist/predicate/planner/relation-planner.js.map +1 -1
  25. package/dist/predicate/safety/predicate-safety-policy.d.ts +8 -0
  26. package/dist/predicate/safety/predicate-safety-policy.d.ts.map +1 -1
  27. package/dist/predicate/safety/predicate-safety-policy.js.map +1 -1
  28. package/dist/predicate/shared/cardinality-risk.d.ts +2 -0
  29. package/dist/predicate/shared/cardinality-risk.d.ts.map +1 -0
  30. package/dist/predicate/shared/cardinality-risk.js +7 -0
  31. package/dist/predicate/shared/cardinality-risk.js.map +1 -0
  32. package/dist/predicate/shared/field-cardinality-guards.d.ts +4 -0
  33. package/dist/predicate/shared/field-cardinality-guards.d.ts.map +1 -0
  34. package/dist/predicate/shared/field-cardinality-guards.js +11 -0
  35. package/dist/predicate/shared/field-cardinality-guards.js.map +1 -0
  36. package/dist/predicate/shared/field-cardinality-policy.d.ts +3 -0
  37. package/dist/predicate/shared/field-cardinality-policy.d.ts.map +1 -0
  38. package/dist/predicate/shared/field-cardinality-policy.js +5 -0
  39. package/dist/predicate/shared/field-cardinality-policy.js.map +1 -0
  40. package/package.json +1 -1
package/README.zh-CN.md CHANGED
@@ -1,513 +1,192 @@
1
- # Mongo Query Normalizer
1
+ # mongo-query-normalizer
2
2
 
3
3
  [English](README.md) | **中文**
4
4
 
5
- 一个面向 **MongoDB 查询对象** **可观测、分层式** 规范化器。它以保守默认策略稳定查询 **shape**,并提供 **`predicate`** 与 **`scope`** 两个带有**文档化、测试兜底契约**的层级(见 [SPEC.zh-CN.md](SPEC.zh-CN.md) 与 [docs/normalization-matrix.zh-CN.md](docs/normalization-matrix.zh-CN.md);英文对照见 [SPEC.md](SPEC.md) 与 [docs/normalization-matrix.md](docs/normalization-matrix.md))。它返回**可预测**的输出与 **metadata**,而不是 MongoDB 查询规划器优化器。
6
-
7
- > **默认策略:** **`shape`** 仅做结构规范化,适合作为**覆盖面最广**的默认路径。 **`predicate`**、**`scope`** 在 **SPEC**、**normalization-matrix** 与 **契约测试** 中有明确边界;仅在需要对应能力且接受「已建模算子」范围时启用;**opaque** 算子保持透传。
8
- >
9
- > **`v0.2.0` 起:** `predicate` 改写面有意收敛到显式验证能力(`eq.eq`、`eq.ne`、`eq.in`、`eq.range`、`range.range`)。高风险组合(如 `null`/缺失语义、数组敏感语义、`$exists`/`$nin`、整对象与点路径混用、opaque 混用)按设计保持保守处理。
10
-
11
- ---
12
-
13
- ## 为什么需要它
14
-
15
- - 查询 **结构** 在不同写法下容易发散。
16
- - 没有稳定层时,**对比、日志、回放** 成本高。
17
- - 需要一层 **低风险** 的 query normalization,默认行为要保守。
18
-
19
- 本库**不以**「自动让查询更快」或「替代 planner」作为卖点。
20
-
21
- ---
22
-
23
- ## 核心特性
24
-
25
- - **按 level 分层**:`shape` → `predicate` → `scope`
26
- - **默认保守**:开箱仅 `shape`(风险最小的结构层)
27
- - **可观测的 `meta`**:变更、规则、告警、哈希、可选统计
28
- - **稳定 / 幂等**(相同 options、未熔断时)
29
- - **不透明(opaque)回退**:不支持的算子以透传为主,不做完整语义改写
5
+ > 安全的 MongoDB 查询规范化器 —— **正确优先于「聪明」**
30
6
 
31
7
  ---
32
8
 
33
- ## 安装
9
+ ## ✨ 它能做什么
34
10
 
35
- ```bash
36
- npm install mongo-query-normalizer
37
- ```
38
-
39
- ---
40
-
41
- ## 快速开始
42
-
43
- ```ts
44
- import { normalizeQuery } from "mongo-query-normalizer";
11
+ **把杂乱的 Mongo 查询,安全地变成干净、稳定、可预期的形态。**
45
12
 
46
- const result = normalizeQuery({
47
- $and: [{ status: "open" }, { $and: [{ priority: { $gte: 1 } }] }],
48
- });
13
+ ```js
14
+ // 之前
15
+ {
16
+ $and: [
17
+ { status: "open" },
18
+ { status: { $in: ["open", "closed"] } }
19
+ ]
20
+ }
49
21
 
50
- console.log(result.query);
51
- console.log(result.meta);
22
+ // 之后
23
+ { status: "open" }
52
24
  ```
53
25
 
54
26
  ---
55
27
 
56
- ## 完整使用说明
57
-
58
- ### 1) 最小可用(推荐默认)
59
-
60
- ```ts
61
- import { normalizeQuery } from "mongo-query-normalizer";
62
-
63
- const { query: normalizedQuery, meta } = normalizeQuery(inputQuery);
64
- ```
65
-
66
- - 不传 `options` 时,默认 `level: "shape"`。
67
- - 适合日志归一化、缓存 key 稳定化、查询 diff 对齐等“低风险结构规范化”场景。
68
-
69
- ### 2) 显式选择 level
70
-
71
- ```ts
72
- normalizeQuery(inputQuery, { level: "shape" }); // 仅结构层(默认)
73
- normalizeQuery(inputQuery, { level: "predicate" }); // 启用已建模谓词整理
74
- normalizeQuery(inputQuery, { level: "scope" }); // 启用 scope 传播/保守剪枝能力
75
- ```
76
-
77
- - `shape`:结构稳定优先,风险最低。
78
- - `predicate`:在已建模算子范围内做去重、合并、矛盾折叠。
79
- - `scope`:在 `predicate` 之上增加继承约束传播与保守分支决策。
80
-
81
- ### 3) `options` 全量示例
82
-
83
- ```ts
84
- import { normalizeQuery } from "mongo-query-normalizer";
85
-
86
- const result = normalizeQuery(inputQuery, {
87
- level: "scope",
88
- rules: {
89
- // shape 相关
90
- flattenLogical: true,
91
- removeEmptyLogical: true,
92
- collapseSingleChildLogical: true,
93
- dedupeLogicalChildren: true,
94
- // predicate 相关
95
- dedupeSameFieldPredicates: true,
96
- mergeComparablePredicates: true,
97
- collapseContradictions: true,
98
- // 排序相关
99
- sortLogicalChildren: true,
100
- sortFieldPredicates: true,
101
- // scope 观测规则(仅观测,不上提改写)
102
- detectCommonPredicatesInOr: true,
103
- },
104
- safety: {
105
- maxNormalizeDepth: 32,
106
- maxNodeGrowthRatio: 1.5,
107
- },
108
- observe: {
109
- collectWarnings: true,
110
- collectMetrics: false,
111
- collectPredicateTraces: false,
112
- collectScopeTraces: false,
113
- },
114
- predicate: {
115
- safetyPolicy: {
116
- // 仅覆盖你关心的字段;其余使用默认值
117
- },
118
- },
119
- scope: {
120
- safetyPolicy: {
121
- // 仅覆盖你关心的字段;其余使用默认值
122
- },
123
- },
124
- });
125
- ```
126
-
127
- ### 4) 用 `resolveNormalizeOptions` 查看最终生效配置
28
+ ## ⚠️ 为什么重要
128
29
 
129
- ```ts
130
- import { resolveNormalizeOptions } from "mongo-query-normalizer";
30
+ 如果你在做动态查询,迟早会遇到:
131
31
 
132
- const resolvedOptions = resolveNormalizeOptions({
133
- level: "predicate",
134
- observe: { collectMetrics: true },
135
- });
32
+ * 重复条件
33
+ * 查询结构不一致
34
+ * 难以调试的过滤器
35
+ * 隐蔽的语义问题
136
36
 
137
- console.log(resolvedOptions);
138
- ```
37
+ 多数工具会试图「优化」查询。
139
38
 
140
- - 适合排查“某个规则为何启用/未启用”。
141
- - 适合在服务启动时打印一次“规范化配置快照”。
39
+ 👉 本库做法不同:
142
40
 
143
- ### 5) 处理返回值(`query` + `meta`)
41
+ > **只应用可证明安全的变换。**
144
42
 
145
- ```ts
146
- const { query: normalizedQuery, meta } = normalizeQuery(inputQuery, options);
43
+ ---
147
44
 
148
- if (meta.bailedOut) {
149
- logger.warn({ reason: meta.bailoutReason }, "normalization bailed out");
150
- }
45
+ ## 🛡️ 设计上就安全
151
46
 
152
- if (meta.changed) {
153
- logger.info(
154
- {
155
- level: meta.level,
156
- beforeHash: meta.beforeHash,
157
- afterHash: meta.afterHash,
158
- appliedRules: meta.appliedRules,
159
- },
160
- "query normalized"
161
- );
47
+ ```js
48
+ // 不会简化(这是对的)
49
+ {
50
+ $and: [
51
+ { uids: "1" },
52
+ { uids: "2" }
53
+ ]
162
54
  }
163
55
  ```
164
56
 
165
- - `query`:规范化后的查询对象。
166
- - `meta`:观测信息(是否变化、规则轨迹、告警、哈希、可选统计与 trace)。
167
-
168
- ### 6) 常见接入模式
57
+ 原因?
169
58
 
170
- ```ts
171
- // A. 在数据访问层统一规范化
172
- export function normalizeForFind(rawFilter) {
173
- return normalizeQuery(rawFilter, { level: "shape" }).query;
174
- }
59
+ 因为 MongoDB 数组可以同时满足两者:
175
60
 
176
- // B. 需要更多收敛能力的离线路径(如批处理)
177
- export function normalizeForBatch(rawFilter) {
178
- return normalizeQuery(rawFilter, { level: "predicate" }).query;
179
- }
61
+ ```js
62
+ { uids: ["1", "2"] }
180
63
  ```
181
64
 
182
- - 在线主路径优先 `shape`。
183
- - `predicate` / `scope` 建议在有明确收益与测试兜底时再启用。
184
-
185
- ### 7) 错误与边界
186
-
187
- - `level` 非法会抛错(例如拼写错误)。
188
- - 不支持或未知算子通常按 opaque 保留,不保证参与语义合并。
189
- - 本库目标是“稳定与可观测”,不是查询优化器。
190
-
191
- ---
192
-
193
- ## 默认行为说明
194
-
195
- - **默认 `level` 为 `shape`**(见 `resolveNormalizeOptions()`)。
196
- - `shape` 默认**不做**谓词级合并。**`scope`** 主路径是继承约束传播与保守分支决策;**`detectCommonPredicatesInOr`** 为**可选、仅观测**规则(告警/轨迹),**从不**做结构上提。
197
- - 默认目标是 **稳定与可观测**,不是「智能优化」。
198
-
199
- ---
200
-
201
- ## 如何选择 level
202
-
203
- - 仅需结构稳定时,用 **`shape`**。
204
- - 需要同字段去重、可建模比较合并、矛盾折叠时,用 **`predicate`**(仅针对已建模算子)。
205
- - 需要继承约束传播、保守剪枝与狭窄覆盖消除时,用 **`scope`**(详见 [SPEC.zh-CN.md](SPEC.zh-CN.md) 与 [docs/normalization-matrix.zh-CN.md](docs/normalization-matrix.zh-CN.md))。**`detectCommonPredicatesInOr`**(开启时)仅观测,不改写结构。
206
-
207
- **行为边界**以 **SPEC**、**normalization-matrix** 与 **`test/contracts/`** 为准,而非仅靠 README 叙述。
208
-
209
- ---
210
-
211
- ## Level 说明
212
-
213
- ### `shape`(默认)
214
-
215
- **推荐默认路径**(风险最小):只做安全结构规范化,例如:
216
-
217
- - 展平复合(`$and` / `$or`)节点
218
- - 移除空复合节点
219
- - 折叠单子复合节点
220
- - 复合子节点去重
221
- - canonical ordering
222
-
223
- ### `predicate`
224
-
225
- 在 `shape` 之上对**已建模**算子做**保守**谓词整理:
226
-
227
- - 同字段谓词去重
228
- - 可建模的比较类谓词合并
229
- - 明确矛盾收敛为不可满足过滤器
230
- - 在 `normalizePredicate` 中,**`$and` 下同名 field 的直接子 `FieldNode` 会先合并**,以便检出诸如 `{ $and: [{ a: 1 }, { a: 2 }] }` 的矛盾
231
-
232
- ### `scope`
233
-
234
- 在 `predicate` 之上:
235
-
236
- - **继承约束传播**(phase-1 白名单)、**保守分支剪枝**;**覆盖消除**仅在狭窄、已测试场景且策略允许时进行
237
- - 可选 **`detectCommonPredicatesInOr`**:仅观测(告警/轨迹);**不改写**查询结构
238
-
239
65
  ---
240
66
 
241
- ## `meta` 说明
242
-
243
- | 字段 | 含义 |
244
- |------|------|
245
- | `changed` | 输出相对输入是否变化(基于哈希) |
246
- | `level` | 实际使用的规范化层级 |
247
- | `appliedRules` / `skippedRules` | 规则应用轨迹 |
248
- | `warnings` | `observe.collectWarnings` 为真时的非致命告警(规则说明、检测文案等) |
249
- | `bailedOut` | 是否触发安全熔断 |
250
- | `bailoutReason` | 熔断原因 |
251
- | `beforeHash` / `afterHash` | 前后稳定哈希 |
252
- | `stats` | 可选的前后树统计(`observe.collectMetrics`) |
253
- | `predicateTraces` | `observe.collectPredicateTraces` 为真时:每字段 planner / 跳过 / 矛盾等轨迹 |
254
- | `scopeTrace` | `observe.collectScopeTraces` 为真时:约束抽取拒绝原因与 scope 决策事件 |
67
+ ## 这不是什么
255
68
 
256
- ---
69
+ * 不是查询优化器
70
+ * 不是索引顾问
71
+ * 不是性能工具
257
72
 
258
- ## 不支持 / opaque 行为
73
+ **绝不会猜测**:
259
74
 
260
- 以下结构通常**只透传或不参与完整语义改写**,例如:
75
+ * 字段基数
76
+ * schema 约束
77
+ * 数据分布
261
78
 
262
- `$nor`、`$regex`、`$not`、`$elemMatch`、`$expr`、geo / text、未知算子等。
79
+ 不确定 **跳过**
263
80
 
264
81
  ---
265
82
 
266
- ## 稳定性策略
267
-
268
- **对外承诺**仅包括:
269
-
270
- - `normalizeQuery`
271
- - `resolveNormalizeOptions`
272
- - 入口导出的 **类型**
273
-
274
- **不属于**对外契约:内部 AST、`parseQuery`、`compileQuery`、各 pass/rule、工具函数等,版本间可能变化。
275
-
276
- ---
277
-
278
- ## 必须明确的原则
279
-
280
- 1. 默认是 **`shape`**。
281
- 2. **`predicate` / `scope`** 可能改变查询结构,但在已建模算子上追求 **语义等价**。
282
- 3. **opaque** 节点不会被语义重写。
283
- 4. 在未熔断时,输出应对相同 options 保持 **幂等**。
284
- 5. 本库 **不是** MongoDB 的 planner optimizer。
285
-
286
- ---
287
-
288
- ## 示例场景
289
-
290
- **在线主路径** —— 使用默认(`shape`);在 `v0.2.0` 中仍是最稳妥的生产基线:
83
+ ## 🚀 快速开始
291
84
 
292
85
  ```ts
293
- normalizeQuery(query);
294
- ```
295
-
296
- **Predicate 或 Scope** —— 显式传 `level`;请结合 [SPEC.zh-CN.md](SPEC.zh-CN.md) 与契约测试理解“可改写”与“保留”边界:
86
+ import { normalizeQuery } from "mongo-query-normalizer";
297
87
 
298
- ```ts
299
- normalizeQuery(query, { level: "predicate" });
88
+ const { query } = normalizeQuery(inputQuery);
300
89
  ```
301
90
 
302
91
  ---
303
92
 
304
- ## 对外 API
93
+ ## 🧠 在架构中的位置
305
94
 
306
- ```ts
307
- normalizeQuery(query, options?) => { query, meta }
308
- resolveNormalizeOptions(options?) => ResolvedNormalizeOptions
95
+ ```text
96
+ Query Builder / ORM
97
+
98
+ normalizeQuery ← (本库)
99
+
100
+ MongoDB
309
101
  ```
310
102
 
311
- 类型:`NormalizeLevel`、`NormalizeOptions`、`NormalizeRules`、`NormalizeSafety`、`NormalizeObserve`、`ResolvedNormalizeOptions`、`NormalizeResult`、`NormalizeStats`、`PredicateSafetyPolicy`、`ScopeSafetyPolicy` 及轨迹相关类型(见包导出)。
312
-
313
- ---
314
-
315
- ## 测试
316
-
317
- ### 测试布局
318
-
319
- 本仓库按 **对外 API**、**规范化 level** 与 **跨 level 契约** 组织测试,并保留更深的语义与回归套件。
320
-
321
- ### 目录职责
322
-
323
- #### `test/api/`
324
-
325
- 覆盖对外 API 与配置面。
326
-
327
- 适合放在此处的验证包括:
328
-
329
- * `normalizeQuery` 的返回形态与顶层行为
330
- * `resolveNormalizeOptions`
331
- * 包导出
332
-
333
- **不要**把「某一 level 专属的规范化行为」放在这里。
334
-
335
- ---
336
-
337
- #### `test/levels/`
338
-
339
- 覆盖每个 `NormalizeLevel` 的行为边界。
340
-
341
- 当前 level:
342
-
343
- * `shape`
344
- * `predicate`
345
- * `scope`
346
-
347
- 每个 level 的测试文件宜聚焦四件事:
348
-
349
- 1. 该 level 的**正向能力**
350
- 2. 该 level **明确未启用**的行为
351
- 3. 与**相邻 level** 的对比
352
- 4. 少量**代表性契约**
353
-
354
- 断言上优先:
355
-
356
- * 规范化后的 **query 结构**
357
- * **跨 level 可观察的差异**
358
- * **稳定的对外 meta**(如 `meta.level` 等)
359
-
360
- 尽量避免过度绑定:
361
-
362
- * warning **逐字全文**
363
- * 内部 **规则 ID 字符串**
364
- * **子句顺序**(除非顺序本身就是契约的一部分)
103
+ 你不是要换掉构建器。
104
+ 你是要**净化它的输出**。
365
105
 
366
106
  ---
367
107
 
368
- #### `test/contracts/`
108
+ ## 🧩 适用场景
369
109
 
370
- 覆盖「应对所有 level 成立」的契约,或与单一 level 无关的默认行为。
371
-
372
- 适合放在此处的内容包括:
373
-
374
- * 默认 level 行为
375
- * 各 level 下的幂等
376
- * 各 level 下的输出不变式
377
- * 各 level 下的 opaque 子树保留
378
- * **`predicate` / `scope` 的正式契约**(支持合并、opaque 保留、scope 策略护栏、规则开关)——见 `test/contracts/predicate-scope-stable-contract.test.js`
379
-
380
- 全 level 套件请配合 `test/helpers/level-contract-runner.js` 使用。
110
+ * 动态筛选 / 搜索 API
111
+ * BI / 报表系统
112
+ * 用户生成的查询
113
+ * 多团队、查询写法不一致的代码库
114
+ * 日志 / 缓存 / 对查询做 diff
381
115
 
382
116
  ---
383
117
 
384
- #### `test/semantic/`
118
+ ## ⚙️ Levels
385
119
 
386
- 对照真实执行行为做**语义等价**验证,确保规范化不改变含义。
120
+ | Level | 作用 | 安全级别 |
121
+ | ----------- | -------------- | ---------- |
122
+ | `shape` | 结构规范化 | 🟢 最稳妥 |
123
+ | `predicate` | 安全的谓词简化 | 🟡 |
124
+ | `scope` | 有限的约束传播 | 🟡 |
387
125
 
388
- 该目录有意与 `levels/`、`contracts/` 分开。
126
+ 默认为 `shape`。
389
127
 
390
128
  ---
391
129
 
392
- #### `test/property/`
393
-
394
- 基于属性的随机测试与变形(metamorphic)行为。
395
-
396
- 适用于:
130
+ ## 📦 输出
397
131
 
398
- * 随机语义检查
399
- * 变形不变式
400
- * 较宽输入空间上的校验
401
-
402
- **不要**把它当作表达「level 边界」的主战场。
403
-
404
- ---
405
-
406
- #### `test/regression/`
407
-
408
- 已知历史失败与手工回归用例。
409
-
410
- 修复了一个不应再犯的 bug 时,把用例加在这里。
411
-
412
- ---
413
-
414
- #### `test/performance/`
415
-
416
- 性能护栏或与复杂度相关的行为。
417
-
418
- 应聚焦性能相关预期,而非一般性的规范化结构细节。
419
-
420
- ---
421
-
422
- ### 辅助文件
423
-
424
- #### `test/helpers/level-runner.js`
425
-
426
- 在指定 level 下执行 `normalizeQuery` 的共享封装。
427
-
428
- #### `test/helpers/level-cases.js`
429
-
430
- 跨 level 测试共用的固定输入;优先把可复用的代表用例加在这里,避免在多个文件里复制同一段 fixture。
431
-
432
- #### `test/helpers/level-contract-runner.js`
433
-
434
- 全 level 契约套件共用的 `LEVELS` 与 `forEachLevel` 等辅助逻辑。
132
+ ```ts
133
+ {
134
+ query, // 规范化后的查询
135
+ meta // 调试 / 轨迹信息
136
+ }
137
+ ```
435
138
 
436
139
  ---
437
140
 
438
- ### 新增测试时的规则
141
+ ## 🎯 设计理念
439
142
 
440
- #### 新增一条规范化规则时
143
+ > 若某次改写可能出错,就不要做。
441
144
 
442
- 先问:
443
-
444
- * 是否属于对外 API 行为?→ 加到 `test/api/`
445
- * 是否仅在某一 level 启用?→ 加到 `test/levels/`
446
- * 是否应对所有 level 成立?→ 加到 `test/contracts/`
447
- * 是否关乎语义保持或随机验证?→ 加到 `test/semantic/` 或 `test/property/`
448
- * 是否针对曾坏过的场景的修复?→ 加到 `test/regression/`
145
+ * 不做 schema 假设
146
+ * 不猜数组语义
147
+ * 不做不安全合并
148
+ * 输出确定
149
+ * 结果幂等
449
150
 
450
151
  ---
451
152
 
452
- #### 新增一个 level 时
153
+ ## 🔍 示例
453
154
 
454
- 至少完成:
155
+ ```ts
156
+ const result = normalizeQuery({
157
+ $and: [
158
+ { status: "open" },
159
+ { status: { $in: ["open", "closed"] } }
160
+ ]
161
+ });
455
162
 
456
- 1. 新增 `test/levels/<level>-level.test.js`
457
- 2. `test/helpers/level-contract-runner.js` 中注册该 level
458
- 3. 确保全 level 契约套件会跑到它
459
- 4. 至少补一条与相邻 level 的**对照**用例
163
+ console.log(result.query);
164
+ // { status: "open" }
165
+ ```
460
166
 
461
167
  ---
462
168
 
463
- ### 测试风格建议
464
-
465
- 宜:
169
+ ## 📚 文档
466
170
 
467
- * 用**基于示例**的用例表达 level 边界
468
- * 断言 **query 形状**
469
- * 做**相邻 level 对照**
470
- * **共享**代表性 fixture
471
-
472
- 忌:
473
-
474
- * 把 level 测试绑死在易变的实现细节上
475
- * 同一 fixture 只改断言表面、重复堆砌
476
- * 把「默认 level」契约塞进某个具体 level 文件
477
- * 把导出/API 测试与规范化行为测试混在同一文件语义里
171
+ * [`SPEC.zh-CN.md`](SPEC.zh-CN.md) 行为规格([English](SPEC.md))
172
+ * [`docs/normalization-matrix.zh-CN.md`](docs/normalization-matrix.zh-CN.md) 规则覆盖([English](docs/normalization-matrix.md))
173
+ * [`docs/CANONICAL_FORM.md`](docs/CANONICAL_FORM.md) 规范形态与幂等性(目前仅英文)
174
+ * [`CHANGELOG.zh-CN.md`](CHANGELOG.zh-CN.md) — 更新日志([English](CHANGELOG.md))
175
+ * [`test/REGRESSION.md`](test/REGRESSION.md) — 复现 property / 语义测试失败(目前仅英文)
176
+ * [`README.md`](README.md) — English README
478
177
 
479
178
  ---
480
179
 
481
- ### 实用对照
180
+ ## 🧪 测试
482
181
 
483
- * `api/`:**库怎么用**
484
- * `levels/`:**每一层做与不做**
485
- * `contracts/`:**哪些必须恒真**
486
- * `semantic` / `property` / `regression` / `performance`:**正确、稳健、效率是否仍成立**
182
+ * 语义等价测试(真实 MongoDB)
183
+ * 基于属性的测试
184
+ * 回归套件
487
185
 
488
186
  ---
489
187
 
490
- ### npm 脚本与 property 测试工具链
491
-
492
- 随机语义测试使用 **`mongodb-memory-server`** 与 **`fast-check`**,在固定文档 schema 与受限算子集合下,对比 normalize 前后真实 `find` 结果(相同 `sort` / `skip` / `limit`,投影 `{ _id: 1 }`),并断言 **`_id` 顺序一致**、返回 **`query` 幂等**;对 opaque 算子仅要求**不崩溃、第二次 normalize 稳定**。生成器见 `test/helpers/arbitraries.js`;**`FC_SEED` / `FC_RUNS` 默认值统一由 `test/helpers/fc-config.js` 管理**(也由 `arbitraries.js` 再导出)。
493
-
494
- 为**避免在线下载** MongoDB 二进制,可在运行语义测试前设置 **`MONGODB_BINARY`**、**`MONGOD_BINARY`** 或 **`MONGOMS_SYSTEM_BINARY`** 指向本机 `mongod`(见 `test/helpers/mongo-fixture.js`)。
495
-
496
- * **`npm run test`**:先 build,再 `test:unit`,再 `test:semantic`。
497
- * **`npm run test:api`**:仅 `test/api/**/*.test.js`。
498
- * **`npm run test:levels`**:`test/levels/**/*.test.js` 与 `test/contracts/*.test.js`。
499
- * **`npm run test:unit`**:除 `test/semantic/**`、`test/regression/**`、`test/property/**` 外的 `test/**/*.test.js`(含 `test/api/**`、`test/levels/**`、`test/contracts/**`、`test/performance/**` 等单元侧用例)。
500
- * **`npm run test:semantic`**:语义 + 回归 + property(环境变量未设时的默认见 `fc-config.js`)。
501
- * **`npm run test:semantic:quick`**:降低 **`FC_RUNS`(脚本内为 45)** 并设 **`FC_SEED=42`**,仍包含 `test/regression/**` 与 `test/property/**`。
502
- * **`npm run test:semantic:ci`**:面向 CI(脚本内 `FC_RUNS=200`、`FC_SEED=42`)。
503
-
504
- 可通过 **`FC_SEED`**、**`FC_RUNS`**、可选 **`FC_QUICK=1`** 覆盖 property 参数(见 `fc-config.js`)。**property 失败如何复现、何时沉淀成固定用例**:见 [`test/REGRESSION.md`](test/REGRESSION.md)。
505
-
506
- 主随机语义等价**不包含**全文、地理、复杂 `$expr`、`$where`、聚合、collation 等;opaque 算子契约见 **`test/contracts/opaque-operators.all-levels.test.js`**。
507
-
508
- ---
188
+ ## 理念
509
189
 
510
- ## 延伸阅读
190
+ 多数查询工具追求「聪明」。
511
191
 
512
- - [SPEC.zh-CN.md](SPEC.zh-CN.md)
513
- - [docs/CANONICAL_FORM.md](docs/CANONICAL_FORM.md)
192
+ 本库追求**正确**。
@@ -37,6 +37,19 @@ function compileFieldNode(node, _normalizeContext) {
37
37
  [node.field]: node.predicates[0].value,
38
38
  };
39
39
  }
40
+ const opCounts = new Map();
41
+ for (const predicate of node.predicates) {
42
+ if (predicate.op === "raw") {
43
+ continue;
44
+ }
45
+ const op = predicate.op;
46
+ opCounts.set(op, (opCounts.get(op) ?? 0) + 1);
47
+ }
48
+ const hasDuplicateOps = [...opCounts.values()].some((c) => c > 1);
49
+ if (hasDuplicateOps) {
50
+ const parts = node.predicates.map((predicate) => compileFieldNode({ type: "field", field: node.field, predicates: [predicate] }, _normalizeContext));
51
+ return { $and: parts };
52
+ }
40
53
  const compiled = {};
41
54
  for (const predicate of node.predicates) {
42
55
  if (predicate.op === "raw") {
@@ -1 +1 @@
1
- {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/compile/compile.ts"],"names":[],"mappings":";;AAMA,oCAEC;AARD,oCAA2D;AAC3D,0CAAkG;AAGlG,wDAAsD;AAEtD,SAAgB,YAAY,CAAC,IAAe,EAAE,iBAAmC;IAC7E,OAAO,WAAW,CAAC,IAAI,EAAE,iBAAiB,CAAU,CAAC;AACzD,CAAC;AAED,SAAS,WAAW,CAAC,IAAe,EAAE,gBAAkC;IACpE,IAAI,IAAA,sBAAa,EAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,IAAA,oBAAW,EAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,IAAA,qBAAY,EAAC,IAAI,CAAC,EAAE,CAAC;QACrB,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,IAAA,mBAAU,EAAC,IAAI,CAAC,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACd,CAAC;IAED,IAAI,IAAA,oBAAW,EAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,iBAAiB,EAAE,CAAC;IAC/B,CAAC;IAED,OAAO,EAAE,CAAC;AACd,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAiB,EAAE,gBAAkC;IAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAEpF,OAAO;QACH,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ;KACK,CAAC;AACjC,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAe,EAAE,iBAAmC;IAC1E,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAClE,OAAO;YACH,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK;SACd,CAAC;IACjC,CAAC;IAED,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAE7C,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACtC,IAAI,SAAS,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC;YAC/B,IAAI,IAAA,4BAAa,EAAC,MAAM,CAAC,EAAE,CAAC;gBACxB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAiC,CAAC,CAAC;YAC/D,CAAC;iBAAM,CAAC;gBACJ,OAAO;oBACH,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM;iBACI,CAAC;YACjC,CAAC;YACD,SAAS;QACb,CAAC;QAED,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,KAAgB,CAAC;IACxD,CAAC;IAED,OAAO;QACH,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,QAAQ;KACE,CAAC;AACjC,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAgB;IACvC,IAAI,IAAA,4BAAa,EAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,GAA8B,CAAC;IAC/C,CAAC;IACD,OAAO,EAAE,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB;IACtB,OAAO,EAAE,GAAG,2BAAmB,EAA6B,CAAC;AACjE,CAAC"}
1
+ {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/compile/compile.ts"],"names":[],"mappings":";;AAMA,oCAEC;AARD,oCAA2D;AAC3D,0CAAkG;AAGlG,wDAAsD;AAEtD,SAAgB,YAAY,CAAC,IAAe,EAAE,iBAAmC;IAC7E,OAAO,WAAW,CAAC,IAAI,EAAE,iBAAiB,CAAU,CAAC;AACzD,CAAC;AAED,SAAS,WAAW,CAAC,IAAe,EAAE,gBAAkC;IACpE,IAAI,IAAA,sBAAa,EAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,IAAA,oBAAW,EAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,IAAA,qBAAY,EAAC,IAAI,CAAC,EAAE,CAAC;QACrB,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,IAAA,mBAAU,EAAC,IAAI,CAAC,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACd,CAAC;IAED,IAAI,IAAA,oBAAW,EAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,iBAAiB,EAAE,CAAC;IAC/B,CAAC;IAED,OAAO,EAAE,CAAC;AACd,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAiB,EAAE,gBAAkC;IAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAEpF,OAAO;QACH,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ;KACK,CAAC;AACjC,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAe,EAAE,iBAAmC;IAC1E,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAClE,OAAO;YACH,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK;SACd,CAAC;IACjC,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACtC,IAAI,SAAS,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YACzB,SAAS;QACb,CAAC;QACD,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;QACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,eAAe,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAElE,IAAI,eAAe,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAC5C,gBAAgB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,iBAAiB,CAAC,CACrG,CAAC;QACF,OAAO,EAAE,IAAI,EAAE,KAAK,EAA6B,CAAC;IACtD,CAAC;IAED,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAE7C,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACtC,IAAI,SAAS,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC;YAC/B,IAAI,IAAA,4BAAa,EAAC,MAAM,CAAC,EAAE,CAAC;gBACxB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAiC,CAAC,CAAC;YAC/D,CAAC;iBAAM,CAAC;gBACJ,OAAO;oBACH,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM;iBACI,CAAC;YACjC,CAAC;YACD,SAAS;QACb,CAAC;QAED,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,KAAgB,CAAC;IACxD,CAAC;IAED,OAAO;QACH,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,QAAQ;KACE,CAAC;AACjC,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAgB;IACvC,IAAI,IAAA,4BAAa,EAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,GAA8B,CAAC;IAC/C,CAAC;IACD,OAAO,EAAE,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB;IACtB,OAAO,EAAE,GAAG,2BAAmB,EAA6B,CAAC;AACjE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEvF;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,eAAe,CAexF"}
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEvF;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,eAAe,CAqBxF"}
package/dist/normalize.js CHANGED
@@ -19,6 +19,9 @@ const detect_common_predicates_in_or_1 = require("./rules/or-common-predicate/de
19
19
  */
20
20
  function normalizeQuery(query, options) {
21
21
  const normalizeContext = (0, normalize_context_1.createNormalizeContext)((0, resolve_1.resolveNormalizeOptions)(options));
22
+ if (normalizeContext.options.predicate.safetyPolicy.allowArraySensitiveRewrite) {
23
+ (0, warnings_1.addWarning)(normalizeContext, "[mongo-query-normalizer] predicate.safetyPolicy.allowArraySensitiveRewrite is deprecated and will be removed in a future release; it no longer enables unsatisfiable (`IMPOSSIBLE_SELECTOR`) deductions for eq/in relations");
24
+ }
22
25
  const beforeNode = (0, parse_1.parseQuery)(query, normalizeContext);
23
26
  recordBeforeObservation(normalizeContext, beforeNode);
24
27
  let workingNode = beforeNode;