Skip to content

Legacy query shim edge cases (for 4.0.3) - #33

Merged
taplin merged 1 commit into
mainfrom
legacy-shim-edge-cases
Oct 4, 2026
Merged

taplin merged 1 commit into
mainfrom
legacy-shim-edge-cases

Conversation

@taplin

@taplin taplin commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator

Edge cases in Sources/PerfectCMongo/shim.h, the layer that keeps the legacy query API working on libmongoc 2. These were found while reviewing 4.0.2. Intended for a 4.0.3 patch release.

1. Invalid legacy $query documents are rejected

Two cases were accepted silently:

  • a $query that isn't a document ({$query: 5}) was treated as an empty filter, so find() and count() matched every document;
  • fields that don't start with $, next to $query, were merged into the filter.

libmongoc 1.x's legacy find rejected both (Cannot mix $query with non-dollar field, Invalid BSON in $query subdocument), and now the shim does too:

  • count() returns the error;
  • find() returns nil, because libmongoc 2 can't hand back a cursor that carries the error and MongoCursor exposes no error;
  • GridFS list(filter:) throws.

A duplicate $query now means the last one wins, as in 1.x; before, the two were merged.

2. count() options from a legacy query are no longer dropped

The shim built the legacy find options and then threw them away.

  • $hint, $maxTimeMS, $comment and $collation now reach count_documents.
  • $orderby, $max, $min and the other find-only modifiers are still ignored, as before, and the doc comment now says so.
  • A negative limit counts like a positive one, as the old count command treated it; before, the $limit stage rejected it. Int.min has no positive equivalent and is now an error.
  • The doc comment notes that count() runs as an aggregate $match, so it can't use $where, $near or $nearSphere. It points to $expr and $geoWithin instead.

3. update(.multiUpdate) with a replacement document is an error

Before, it replaced one document and reported success. It now returns an error and writes nothing. The message is the server's own wording for this case ("multi update is not supported for replacement-style update"). The server reports it as code 9; here it's a client-side MONGOC_ERROR_COMMAND_INVALID_ARG.

Testing

There are 4 new tests. 14 of their checks fail on main; the rest are baseline checks that pass either way. The $comment test uses the profiler in a database of its own, which it drops afterwards, and skips if the server refuses profiling.

The full suite (40 tests) passes against mongod 8.3.11 with libmongoc 2.5.5:

  • on macOS, debug and release builds;
  • on Linux via Scripts/test-linux.sh.

An adversarial review checked these behaviours against the server, against the libmongoc 1.30 and 2.0/2.5 sources, and with an ASan/UBSan probe of the shim. It found no blockers. Two left as-is:

  • the bulk update(updates:) path still replaces one document when given a replacement document, since it has no multi flag;
  • GridFS list(filter:) throws a generic error rather than the specific message.

🤖 Generated with Claude Code

…ti replace

_perfect_split_legacy_query treated a $query that isn't a document as an
empty filter, so find() and count() matched every document. It also
merged non-$ fields next to $query into the filter. libmongoc 1.x's
legacy find rejected both ("Cannot mix $query with non-dollar field",
"Invalid BSON in $query subdocument"), and now the shim does too:
count() returns the error, find() returns nil (libmongoc 2 can't hand
back a cursor carrying the error, and MongoCursor exposes no error), and
GridFS list(filter:) throws. A duplicate $query now means the last one
wins, as in 1.x; before, the two were merged. (1.x's legacy count never
unwrapped $query at all; current servers reject a raw $query in count.)

_perfect_collection_count built the legacy find options and threw them
away. hint, maxTimeMS, comment and collation now reach count_documents.
$orderby, $max, $min and the other find-only modifiers are still ignored,
and the doc comment says so. A negative legacy limit is passed as its
absolute value, as the old count command treated it; before, the $limit
stage rejected it. INT64_MIN, which has no absolute value, is an error.
The doc comment also notes that count() can't use $where, $near or
$nearSphere, since count_documents runs the query as a $match.

update() with .multiUpdate and a replacement document replaced one
document and reported success. It now returns an error without writing,
with the server's own message for this case ("multi update is not
supported for replacement-style update"; the server's code is 9, this
is a client-side MONGOC_ERROR_COMMAND_INVALID_ARG).

Tests: 4 new tests. 14 of their checks fail on main (the rest are
baseline checks that pass either way). The comment check uses the
profiler in a database of its own, which it drops, and skips when the
server refuses profiling. All 40 tests pass against mongod 8.3.11 on
macOS (debug and release) and on Linux (Scripts/test-linux.sh).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@taplin
taplin merged commit 490f490 into main Oct 4, 2026
6 checks passed
@taplin
taplin deleted the legacy-shim-edge-cases branch October 4, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant