Skip to content

fix(driver-mongodb): the $contains family is case-SENSITIVE — drop the hardcoded $options: 'i' (#6682) - #7625

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-6682-mongodb-contains-case
Aug 11, 2026
Merged

fix(driver-mongodb): the $contains family is case-SENSITIVE — drop the hardcoded $options: 'i' (#6682)#7625
huangyiirene merged 1 commit into
mainfrom
claude/issue-6682-mongodb-contains-case

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Part of #6682 — the driver-mongodb half only. The driver-memory half stays frozen under #5499, so this card must remain OPEN after merge.

What changed

translateFieldOperators (packages/drivers/driver-mongodb/src/mongodb-filter.ts) lowered $contains / $notContains / $startsWith / $endsWith to a $regex with a hardcoded $options: 'i' beside it — MongoDB's full-Unicode case fold. That flag is off all four arms.

// before                                   // after
{ name: { $regex: 'acme', $options: 'i' } }  { name: { $regex: 'acme' } }
{ name: { $not: { $regex: 'acme',            { name: { $not: { $regex: 'acme' } } }
                  $options: 'i' } } }

{ name: { $contains: 'acme' } } no longer returns ACME Corp. escapeRegex is untouched — dropping the flag changes which cases match, never which characters are metacharacters, so a.b still matches a.b and not axb.

$notContains needed the treatment in one place, not two: its $not-wrapped operator document is the only other site, and a flag left there would have made the negation exclude rows the positive form includes.

$icontains is untouched. Its fold has always lived in the pattern (asciiCaseInsensitiveRegexSource, one [Aa] class per ASCII letter), which is why it was already the one arm without the flag. After this change $options is spelled nowhere in the emitter — the right shape, since $options is a RETIRED operator this same driver refuses on input (#5702).

Why — and why both directions of the defect mattered

#4706 Q2 = A: the $contains family is case-SENSITIVE on every backend. The fold over-matched — rows the filter excludes were returned, which on an RLS read scope is over-reach rather than a loose filter (#3948) — and it folded the whole Unicode range, overshooting the ASCII-only boundary #4706 Q1 = A holds $icontains to.

Multi-face declaration — the whole scoreboard, with evidence

face { name: { $contains: 'acme' } } vs ACME Corp evidence
driver-sql / -sqlite-wasm / -turso (local + remote) does NOT match #6518GLOB on the SQLite dialects, LIKE over a binary cast on MySQL, LIKE unchanged on Postgres; turso-local-remote-text-parity.test.ts holds both transports to the same rows
@objectstack/formula matchesFilterCondition does NOT match already compliant (String.includes), re-measured #6993
ObjectQL having does NOT match already compliant; having-filter-text-conformance.test.ts
service-analytics (both SQL compilers) does NOT match already compliant — Postgres-shaped LIKE
driver-mongodb does NOT match — FIXED HERE mongodb-filter-text-conformance.test.ts, this PR
driver-memory — query + analytics faces MATCHES out of scope — frozen under #5499; DEBT row retained
driver-memory — reference matcher does NOT match ⛔ out of scope — that package's two-face divergence is #6682's remaining half

One more face worth naming, because "not mentioned" reads as "not checked": this driver has no second lowering. find / count / update / delete (mongodb-driver.ts) and the aggregation $match (mongodb-aggregation.ts:164) all route through the one translateFilter, so unlike driver-memory there was no analytics face to align separately.

Conformance cell CLEARED — measured, not argued

The FILTER_TEXT_CASES cell is enrolled and its DEBT row deleted in this PR, because scripts/check-driver-conformance.mjs judges a cell by IMPORT: deleting the row without the suite fails CONSUMED, keeping it alongside the suite fails RECONCILED.

The card assumed enrollment was blocked on #6520. It is not — $icontains (requirement 1) landed on this driver in #6520, so this change closes the last open requirement. Verified by running the case-set against origin/main @ 744b8f5 before the fix:

Tests  5 failed | 17 passed (22)
× $contains is case-SENSITIVE — a lower-case comparand misses the upper-case row
    emitted: {"name":{"$regex":"acme","$options":"i"}}: expected [ '1', '2' ] to equal [ '2' ]
× $contains is case-SENSITIVE — an upper-case comparand misses the lower-case row
× $startsWith is case-SENSITIVE
× $endsWith is case-SENSITIVE
× $notContains is case-SENSITIVE, and negation does not widen it

Exactly the five requirement-2 rows — every $icontains row, every literal-comparand row and all five rejection rows already passed. After the fix: 22 passed. So the flag was the whole remaining gap, and nothing was widened to reach green.

New suite mongodb-filter-text-conformance.test.ts:

  • Server-free. This package's real-mongod suites are opt-in (OS_TEST_MONGODB_MEMORY_SERVER_ENABLED=1, driver-mongodb 测试:两个套件并发下载 MongoDB 二进制,mongodb-memory-server 的 rename 竞态让全绿测试 exit 1(队列 flaky) #5517), so a suite needing a server would not run in CI. Same judgement as mongodb-filter-logic-translation.test.ts and mongodb-aggregation-translation.test.ts. The real-mongod half remains absent and is recorded as such rather than implied.
  • It evaluates the emitted documents over FILTER_TEXT_ROWS rather than pinning their spelling, and the matcher models $options instead of ignoring it — a matcher that dropped the flag would have reported the fixed answer from the broken translator. Its own discrimination is proved: { $regex: 'acme', $options: 'i' } selects ['1','2'], the bare pattern selects ['2'].
  • Refusal rows assert code + status per ADR-0112, not a bare toThrow() — this driver is the reason that bar exists (its default: arm threw a bare new Error until drivers: $regex 响亮拒收 + $icontains 各后端实现(#4706 裁决 B 案 · 驱动半边) #5702).
  • A guard row pins the five declared rejection-case names, so a new refusal joining the table forces a re-read instead of silently skipping.

Gate now reads: driver-mongodb … FILTER_TEXT ok AGGREGATION ok38 covered cell(s), 2 in the DEBT ledger (both driver-memory).

Pins FLIPPED, not deleted

Every pin that encoded the folded answer now asserts the new substance:

  • mongodb-filter.test.ts — the five string-operator pins and the parseFilterAST contains row, plus a new family-wide assertion that no arm emits $options at all.
  • mongodb-icontains.test.ts⚠️ one deviation from the dispatch note, reported rather than worked around. That file carried a pin explicitly asserting the defect: it('does not disturb the $contains family — still \$options: i` (the open drivers(memory, mongodb): the $contains family still folds case — the last two backends left on the wrong side of #4706 Q2 = A #6682 defect)'). It is labelled in-file as "pinned here as the current state", i.e. authored to go red exactly when this card lands. It is flipped to assert the ruled answer and extended to assert the two arms have **not** collapsed into one ($contains→ bare pattern,$icontains[Aa][Cc][Mm][Ee]), which is what that row has always guarded. **Every $icontains` assertion in that file is unchanged and green.**

Refusal pins for genuinely invalid shapes are verbatim.

Docs and docblocks corrected

Sites that recorded this driver as folding: packages/spec/src/data/filter-text-conformance.ts (header + the $contains section + one case note), packages/spec/src/data/filter.zod.ts, and the user-facing content/docs/protocol/objectql/query-syntax.mdx (two callouts — the second also still described the SQL family as unaligned, stale since #6518, corrected in the same breath rather than left half-true). The historical "measured before the ruling" table in filter.zod.ts is deliberately untouched — it is a record of what was true then.

⛔ No content/docs/releases/ edit. The changeset is .changeset/mongodb-contains-case-sensitive.mdpatch for @objectstack/driver-mongodb, and it tells a user relying on the fold to write $icontains.

Gates run

gate result
node scripts/check-driver-conformance.mjs OK — 38 covered, 2 DEBT, 0 exempt
node scripts/check-driver-conformance.mjs --self-test OK
driver-mongodb suite 325 passed, 143 skipped (5 files skipped — real-mongod, opt-in)
spec filter-text-conformance + filter-operator-vocabulary 36 passed
tsc --noEmit — driver-mongodb, spec clean
build — spec, driver-mongodb clean

mongodb-memory-server is not downloadable in this environment (proxy 403), so the real-mongod suites were not executed; they are skipIf-gated and skipped on main too. All verification is at the translateFilter translation layer.


Generated by Claude Code

…the hardcoded `$options: 'i'` (#6682)

`translateFieldOperators` lowered `$contains` / `$notContains` / `$startsWith` /
`$endsWith` to a `$regex` with `$options: 'i'` beside it — MongoDB's full-Unicode
case fold. #4706 Q2 = A rules that family case-SENSITIVE on every backend, and
this driver was the last face still on the wrong side of it (#6518 flipped the
SQL family; `formula`, ObjectQL's `having` and service-analytics were already
compliant).

Both directions of the defect mattered: the fold OVER-matched — rows the filter
excludes came back, which on an RLS read scope is over-reach rather than a loose
filter (#3948) — and it folded the whole Unicode range, overshooting the
ASCII-only boundary Q1 = A holds `$icontains` to.

`escapeRegex` is untouched, so comparands stay LITERAL; dropping the flag changes
which CASES match, never which characters are metacharacters. `$icontains` is
untouched too — its fold has always lived in the pattern
(`asciiCaseInsensitiveRegexSource`), which is why it was the one arm without the
flag. `$options` is now spelled nowhere in the emitter, which is the right shape:
it is a RETIRED operator this same driver refuses on input (#5702).

The `FILTER_TEXT_CASES` cell is CLEARED in the same PR, because the gate judges a
cell by IMPORT and both halves have to move together. Measured rather than
argued: driving the case-set against `origin/main` @ `744b8f5` failed exactly the
five case-sensitivity rows and passed the other twelve — every `$icontains` row,
every literal-comparand row and all five rejection rows — so the flag was the
whole remaining gap.

- NEW `mongodb-filter-text-conformance.test.ts`: server-free (this package's
  real-mongod suites are opt-in per #5517), imports the marker and evaluates the
  emitted documents over `FILTER_TEXT_ROWS`. Rejection rows assert `code` +
  `status` per ADR-0112, and the matcher models `$options` rather than ignoring
  it, so it fails the folded translator.
- The mongodb DEBT row is deleted from `scripts/check-driver-conformance.mjs`.
  `driver-memory`'s row stands — that half stays frozen under #5499.
- Pins FLIPPED, not deleted: `mongodb-filter.test.ts` and the `$contains`
  contrast row in `mongodb-icontains.test.ts` now assert the ruled answer, plus a
  new family-wide assertion that no arm emits `$options` at all. Every
  `$icontains` assertion is unchanged.
- Docs and spec docblocks corrected where they recorded this driver as folding.

Part of #6682 — the driver-memory half stays open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VqhD3BLddBixYd6y46mM2L
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 11, 2026 9:15am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/driver-mongodb, @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-mongodb, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-mongodb)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/driver-mongodb, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-mongodb, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/driver-mongodb, @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/driver-mongodb, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@huangyiirene
huangyiirene marked this pull request as ready for review August 11, 2026 10:17
@huangyiirene
huangyiirene added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 7e9e555 Aug 11, 2026
39 of 41 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-6682-mongodb-contains-case branch August 11, 2026 10:33
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…jectstack-ai#7854) (objectstack-ai#7890)

objectstack-ai#7723 took the `i` flag off driver-memory's filterSubstringPattern,
closing objectstack-ai#6682. Two packages/spec docblocks still pointed forward at
that landing as a future event:

- filter.zod.ts's per-backend history table (kept as history per
  objectstack-ai#7625) had a driver-memory cell reading "the last one standing,
  until objectstack-ai#6682" -- accurate as history, but the "until objectstack-ai#6682" tail
  described a future that has since landed. Closed in past tense
  without flattening the historical framing; the table's other rows
  (including driver-mongodb's pre-objectstack-ai#7625 mechanism) are untouched.
- filter-text-conformance.ts's @see objectstack-ai#6682 line read "mongodb landed,
  memory open", contradicting the file's own header 37 lines earlier
  (already correct since objectstack-ai#7723). Now reads "mongodb and memory both
  landed".

Fixes objectstack-ai#7854


Claude-Session: https://claude.ai/code/session_016YBUGvukaeVu9DjKdsHJa9

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants