Skip to content

driver-memory's reference matcher still answers $notContains / $nin the pre-ruling way on a no-value row — the #5499 freeze that excused it dissolved 2026-08-11, so the divergence is now unexcused and untracked #13166

Description

@os-litant

Split out of #13089 per its triage instruction ("若测出 HAVING 分歧在解冻后仍真实存在 ⇒ 停下,把它立成 domain:engine 的独立卡"). #13089 remains open and is not addressed by this card; that card's PR delivers only its sites 1-2 (the two pm-dispatch reference files).

The measurement

Run on branch claude/issue-13089-freeze-residue at merge-base 56470d86b, by execution (not by reading), against packages/drivers/driver-memory/src/memory-matcher.tsmatch(). Fixture: rows 1 (name: 'alpha-one'), 2 (name: 'beta'), 3 (no value) — measured under BOTH readings of "no value", name: null and the key absent. The platform-settled answer (#5146 extended by #5298 option A, re-affirmed after the #5299 reversal was costed and withdrawn) is that a no-value row SATISFIES a negation-carrying operator, i.e. ['2','3']:

Operatorname: nullkey absentruling answer
$ne: 'alpha-one'['2','3'] OK['2','3'] OK['2','3']
$nin: ['alpha-one']['2','3'] OK['2'] DIVERGENT['2','3']
$notContains: 'one'['2'] DIVERGENT['2'] DIVERGENT['2','3']

Three of six cells diverge. This reproduces exactly the cell already recorded in the filter-logic-conformance.ts measurement table ("driver-memory reference matcher | no — DIVERGENT, frozen (#5499) | MATCH on a null value, no on a missing key"), so the divergence is real, unchanged, and now unexcused.

Mechanism

Two independent causes in memory-matcher.tscheckCondition:

  1. The pre-switch guard if (value === undefined && op !== '$exists' && op !== '$ne' && op !== '$null') return false; — its allowlist names $ne but not $nin / $notContains, so a MISSING key short-circuits to "no match" for those two before their arms ever run.
  2. The $notContains arm itself: if (typeof value !== 'string' || value.includes(target)) return false; — a null value is not a string, so it fails on the type test rather than on the predicate. That is the name: null half, which the guard above does not reach.

driver-mongodb is NOT part of this: translateFieldOperators maps $nin straight through and compiles $notContains to { $not: { $regex } }, both of which match a missing/null field in MongoDB. It already agrees with the ruling.

Why this is now a card rather than a comment

Two in-code annotations still spell the freeze as LIVE and use it to excuse the divergence — they are #13089's sites 3-4 and were deliberately left untouched:

Both are wrong on two counts now: the freeze dissolved on 2026-08-11 (corroborated in the head note of packages/spec/src/data/aggregation-conformance.ts), and the driver-mongodb half of the claim was never true for this operator family. Rewriting them to past tense without deciding the divergence would have converted an actionable defect into settled-looking prose, which is what the #13089 triage forbade.

What a fix owes

  • Decide the direction first: align driver-memory's reference matcher to the ruling (the include direction), or record a measured, dated exemption. Note this matcher is the reference the SQL family was aligned TO for $not 的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {} 一个是 TRUE 一个是 FALSE #5146, so moving it is not local.
  • $exists is a separate, still-open cell — filter-logic-conformance.ts records that driver-memory's live mingo path and driver-mongodb both still read key-presence rather than has-value, which is why no $exists row can be enrolled yet. Not necessarily this card's scope, but it is the neighbouring cell.
  • Enrolment, not a row: the head note of filter-logic-conformance.ts is explicit that a row added ahead of a backend is just a red gate, and that the DEBT ledger in scripts/check-driver-conformance.mjs is per (driver x case-set), with no spelling for "fails one row of thirty-six".

Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions