Skip to content

drivers(memory, mongodb): the $contains family still folds case — the last two backends left on the wrong side of #4706 Q2 = A #6682

Description

@os-zhuang

Finding, filed unassigned from the #6518 implementation. Not a request to unfreeze anything — it exists so the two FILTER_TEXT_CASES DEBT rows that survive #6518 have a successor to point at, instead of pointing at a closed issue.

What #6518 left behind

#4706 Q2 = A rules the $contains family ($contains / $notContains / $startsWith / $endsWith) case-SENSITIVE on every backend. After #6518 the scoreboard is:

face { name: { $contains: 'acme' } } against ACME Corp verdict
driver-sql / -sqlite-wasm / -turso (local + remote) does NOT match ✅ ruled behaviour (#6518)
@objectstack/formula matchesFilterCondition does NOT match ✅ already compliant (String.includes)
objectql having does NOT match ✅ already compliant
service-analytics (both SQL compilers) does NOT match ✅ already compliant on the dialect they emit for
driver-memory — query path + analytics face MATCHES new RegExp(escapeRegex(v), 'i')
driver-memory — reference matcher does NOT match ⚠️ this package answers ONE operator two ways
driver-mongodb MATCHES $regex with a hard-coded $options: 'i'

Both defects are over-matching — rows the filter excludes are returned — and both fold the whole Unicode range, so they also overshoot the #4706 Q1 = A ASCII-only boundary that $icontains is held to. On an RLS read scope a wider predicate is over-reach, not a loose filter (#3948).

The driver-memory row is the sharper one: its two faces disagree with each other, which is the divergence class #5374 closed between the other two faces of that package.

Why #6518 did not do it

The #5499 freeze (maintainer, 2026-08-05) suspends implementation investment in driver-memory and driver-mongodb. #6518 was dispatched with that freeze restated as a ruling, so both packages were explicitly out of scope; their FILTER_TEXT_CASES cells stay DEBT rather than being cleared or quietly widened.

Why this is separate from #6520

#6520 is the $icontains programme (requirement 1: implement a new operator, gated on taking $icontains into the spec FILTER_OPERATORS allowlist). This is requirement 2: an operator both packages already implement returns the wrong rows. Different requirement, different risk — #6520's cost is the vocabulary change rippling through allowlist consumers, while this one is a one-line flag on each side plus the row-set pins that flip with it.

They are, however, natural PR-mates: both touch the same two translateFieldOperators / normalizeFieldOperators sites, and clearing a FILTER_TEXT_CASES cell needs BOTH requirements answered before the case-set can be imported (scripts/check-driver-conformance.mjs judges a cell by that import, so a half-answered cell must not import it).

Suggested scope

  1. driver-memory: drop the 'i' flag from the $contains-family lowering on the query path and the analytics face, and align the reference matcher — one answer per operator, per driver-memory analytics 面的 $notContains 编译成裸 mingo {$not: 'x'},该谓词不约束任何行 —— 结果被放大到全表 #5374.
  2. driver-mongodb: drop the hard-coded $options: 'i' in mongodb-filter.ts's translateFieldOperators. escapeRegex already makes the comparand literal, so the literal-comparand cases keep holding.
  3. Flip the row-set pins that currently encode the folded answer, asserting the NEW substance rather than deleting the assertion.
  4. Re-grade the two FILTER_TEXT_CASES DEBT rows in scripts/check-driver-conformance.mjs — they can only be deleted once requirement 1 (JS 求值面全体拒收 $icontains(driver-memory 两面 / driver-mongodb / objectql having / formula)—— SQL 族已实现,同一 filter 在内存 double 上抛错 #6520) is answered too, since coverage is judged by importing the whole case-set.

Anchors verified on origin/main @ d13f627: driver-memory/src/memory-driver.ts (normalizeFieldOperators, the RegExp-'i' query path), driver-memory/src/memory-matcher.ts (match(), String.includes), driver-mongodb/src/mongodb-filter.ts (translateFieldOperators).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions