Skip to content

release: 1.2.0 - #4

Merged
MobyNL merged 5 commits into
mainfrom
release/1.2.0
Aug 17, 2026
Merged

release: 1.2.0#4
MobyNL merged 5 commits into
mainfrom
release/1.2.0

Conversation

@MobyNL

@MobyNL MobyNL commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Two features, then the release commit.

Load Document and Insert Document From File

A fixture document read from an Extended JSON file rather than written into a suite, so
$oid and $date arrive as the BSON types MongoDB compares against. Two kinds of hole —
${name} from the calling suite, {name} from the keyword's arguments — plus dotted-path
overrides of fields the file already fills. A new document_path import argument names the
lookup directory and does nothing else.

Explain Query and Collection Should Have Index

A query that matches nothing and reports no error was documented for exactly one cause, a
string _id against an ObjectId. Explain Query covers the rest: it reports the plan the
server chose and, most usefully, index_bounds — the values it actually searched for.

${plan}    Explain Query    collection_name=readings    _id.deviceId=${device_id}    _id.date=${date}
Log    ${plan.index_bounds}
Explain of {'_id.deviceId': 'device-1', '_id.date': datetime.datetime(2026, 1, 8, 0, 0)} on 'readings':
  stage            FETCH
  index_name       _id.deviceId_1__id.date_1
  collection_scan  False
  keys_examined    1
  docs_examined    1
  returned         1
  index_bounds
    _id.deviceId  ["device-1", "device-1"]
    _id.date      [new Date(1767830400000), new Date(1767830400000)]

Design notes worth reviewing:

  • The summary is derived, not copied, because the explain document's shape varies while
    the questions do not: SBE nests stages under queryPlan, sharded clusters report per
    shard, and the _id fast path is EXPRESS_IXSCAN on MongoDB 8 but IDHACK before it.
    A scan is detected by the presence of a COLLSCAN, so there is no version check.
  • It runs through database.command rather than cursor.explain(), which pymongo pins to
    allPlansExecution; that is what makes executionStats the default and queryPlanner
    available.
  • It is a diagnostic and asserts nothing, collection_scan included — MongoDB rightly
    scans small collections, so such an assertion would pass on production-shaped data and
    fail on a freshly seeded test collection with nothing wrong. Collection Should Have Index is the assertion, reading index definitions rather than a plan so it cannot flake,
    and asking by fields rather than by the derived name.

Verification

  • 368 unit tests, MongoDBLibrary/keywords.py at 100% coverage; mypy, ruff and
    robocop check atest clean.
  • New atest/explain_tests.robot, 11/11 against MongoDB 8, seeding 200 documents keyed by
    a compound _id. One test reproduces the field-order trap end to end: the same _id
    with its fields reversed explains as returned: 0 while Find Document With Query
    returns None.
  • Explain unit tests use canned explain documents on the MagicMock fixture, since mongomock
    implements no explain at all.

🤖 Generated with Claude Code

MobyNL and others added 5 commits August 17, 2026 14:16
`Load Document` reads a fixture document from a JSON file, and `Insert Document
From File` reads one and inserts it in a single step. A document written into a
suite is fine until a second test needs it, and then it is copied and the copies
drift.

The file is read as MongoDB Extended JSON, so `$oid`, `$date`, `$numberInt` and
`$numberDouble` arrive as the BSON types MongoDB compares against rather than as
text that silently matches nothing. Update operators are `$`-prefixed too and
are passed through untouched, so a file can hold an update as readily as a
document.

Two kinds of hole, written differently because they are filled from different
places: `${name}` is a Robot Framework variable resolved from the calling
suite, and `{name}` is a template placeholder filled from the keyword's named
arguments. Any field the file already fills can be overridden by its dotted
path, which is the part a suite cannot do for itself, since `&{dict}` expansion
merges only one level deep. Either kind of hole left unfilled fails the keyword,
because the literal text is a perfectly insertable string that would seed a
document looking almost right.

The new `document_path` import argument names the directory that documents given
by file name are looked up in. It only removes the repetition: a path given to
the keyword works with or without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A query that matches nothing and reports no error is this library's most common
failure, and only one cause of it — a string `_id` against an ObjectId — was
documented. `Explain Query` covers the rest: it asks the server how it answered
the query and returns a flat summary of the plan, the most valuable field being
`index_bounds`, the values the server actually searched for. Comparing those
with what the suite passed is usually the whole diagnosis.

It takes the query either way the find keywords take it, as free arguments or as
a `query` document, so it drops in beside a failing call without rewriting it.
The summary is logged at INFO a field to a line, since Robot Framework keeps
newlines and indentation in `log.html` and a summary on one line is readable by
nobody; the whole explain document is logged at DEBUG, indented, and returned
under `raw`.

The summary is derived rather than copied, because the explain document's shape
differs by server version and topology while the questions do not: a slot-based
plan nests its stages under `queryPlan`, a sharded cluster reports per shard,
and the `_id` fast path is `EXPRESS_IXSCAN` on MongoDB 8 and `IDHACK` before it.
A collection scan is therefore detected by the presence of a `COLLSCAN` rather
than by an allowlist of index stage names, so no version check is involved.

The keyword is a diagnostic and asserts nothing, `collection_scan` included:
MongoDB rightly chooses a scan on a small collection, where reading it beats an
index lookup plus a fetch, so an assertion that no scan happens would pass
against production-sized data and fail against a freshly seeded test collection
with nothing wrong. Where a suite needs an index, `Collection Should Have Index`
says so directly and cannot flake, reading the collection's index definitions
rather than a plan. It asks by fields rather than by name — the name is derived
from the fields, and the fields are what the queries depend on — and compares
their order, because a compound index serves its fields left to right.

Both are read-only. The unit tests feed the keyword canned explain documents,
mongomock having no `explain` at all; the new acceptance suite covers a real
server answering a real query against a collection keyed by a compound `_id`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Documents read from Extended JSON files, and the two keywords for a query that
returns nothing without erroring: `Explain Query` and `Collection Should Have
Index`.

Regenerates the keyword documentation so the version stamp GitHub Pages serves
matches the release; CI only builds libdoc as a check, so the committed file is
what is published.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three keywords could describe something that never happened, which is the
failure mode a test library can least afford.

Explain Query walked the whole explain document, so a rejected candidate plan
could report a collection scan for a query the server answered with an index,
or lend its index name and bounds to a query that scanned. _explain_stages now
skips rejectedPlans and allPlansExecution, which fixes the summary and the
per-shard summaries at once.

Load Document filled placeholders after ${...} substitution, so a variable
whose value contained braces was re-read as a template and failed on a hole the
file never declared. The placeholders a file declares are now read before
substitution: a variable's value is data.

Collection Should Have Index ended its message with a dangling "It has: ." when
the collection did not exist, which hid the likeliest cause of the failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Python 3.13 rewrote the decoder's messages, and a trailing comma is now
reported at the comma rather than at the token after it, so the test failed on
3.13 and 3.14 while the library was doing exactly what it should. What matters
is that the failure names the file and carries a position, which is what is
asserted now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@MobyNL
MobyNL merged commit bd366b5 into main Aug 17, 2026
12 checks passed
@MobyNL
MobyNL deleted the release/1.2.0 branch August 19, 2026 07:47
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