feat: support toArray no-op, runCommand, explain on read ops, and empty-statement no-op#29
Merged
feat: support toArray no-op, runCommand, explain on read ops, and empty-statement no-op#29
Conversation
…ty-statement no-op
Four mongosh compatibility fixes drawn from production gomongoFallback
telemetry. Each one was fronting a real fallback to mongosh; with these
in place ~9 distinct user-query shapes that previously fell back now run
natively in gomongo.
- toArray(): treat as a no-op cursor terminator alongside pretty(). The
result rows are already materialized into Result.Value, so the chained
call is purely cosmetic in mongo shell. Wild shape:
db.coll.find({…}).limit(N).toArray().
- db.runCommand({…}): generic escape hatch. Adds OpRunCommand and a
thin executor that hands the command body to mongo.Database.RunCommand
and returns the bson.D server response unchanged. Used in the wild
for the legacy `count` command form, and useful for any server
command without a dedicated typed wrapper.
- explain() on read operations: a trailing .explain() / .explain(verbosity)
on find / aggregate / count / distinct rewrites the operation into
{explain: <innerCommand>, verbosity: <v>} run via runCommand. Also
accepts the aggregate-option form db.coll.aggregate([…], {explain: true}).
Verbosity defaults to "queryPlanner" and is validated against the three
accepted values. Write-op explain (update/delete) is left out for now.
- Empty / comment-only input: gomongo previously rejected pure-comment
statements with "empty statement: …". mongosh treats them as no-op
successes; now gomongo does too via a new OpNoOp that the executor
handles by returning an empty Result.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR improves gomongo’s mongosh compatibility by adding support for no-op cursor terminators, a generic db.runCommand() escape hatch, explain() for read operations (including aggregate {explain: true}), and treating comment-only/empty input as a no-op operation.
Changes:
- Add
OpNoOphandling so comment-only / whitespace-only input returns an empty successful result. - Add
OpRunCommand+ argument validation + executor implementation to pass throughdb.runCommand({...}). - Add
explain()rewriting for supported read ops into anexplainwrapper executed viarunCommand, and treattoArray()as a no-op cursor terminator.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| types/operation_type.go | Adds new operation types (OpNoOp, OpRunCommand). |
| internal/translator/types.go | Extends Operation with Command and Explain fields. |
| internal/translator/translator.go | Treats empty/comment-only input as OpNoOp instead of erroring. |
| internal/translator/translate.go | Adds runCommand, toArray no-op, and explain tagging + rewrite hook. |
| internal/translator/explain.go | Implements explain tagging, validation, and command-rewrite builders. |
| internal/translator/database.go | Adds runCommand() argument extraction/validation. |
| internal/translator/collection.go | Adds aggregate {explain: true} option support. |
| internal/executor/executor.go | Executes OpNoOp locally and routes OpRunCommand to executor. |
| internal/executor/admin.go | Implements executeRunCommand() using existing runCommand() helper. |
| client.go | Documents new OpRunCommand and OpNoOp result shapes. |
| collection_test.go | Adds tests for toArray() no-op and explain() on read ops. |
| admin_test.go | Adds tests for runCommand() and comment-only input no-op behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
7
to
14
| const ( | ||
| OpUnknown OperationType = iota | ||
| // OpNoOp represents an input that contains no executable statements | ||
| // (e.g., the entire input is a JS comment or whitespace). Executors | ||
| // return an empty Result for OpNoOp without contacting the server. | ||
| OpNoOp | ||
| OpFind | ||
| OpFindOne |
| } | ||
| if len(op.Min) > 0 { | ||
| cmd = append(cmd, bson.E{Key: "min", Value: op.Min}) | ||
| } |
Comment on lines
+132
to
+136
| func buildCountCommand(op *Operation) bson.D { | ||
| cmd := bson.D{{Key: "count", Value: op.Collection}} | ||
| if len(op.Filter) > 0 { | ||
| cmd = append(cmd, bson.E{Key: "query", Value: op.Filter}) | ||
| } |
| } | ||
| if len(op.Filter) > 0 { | ||
| cmd = append(cmd, bson.E{Key: "query", Value: op.Filter}) | ||
| } |
rebelice
approved these changes
May 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four mongosh compatibility fixes drawn from production
gomongoFallbacktelemetry. Combined with omni#127 (trailing-comma tolerance), these clear ~13 of the 25 deprecation blockers from the most recent batch of fallback events, with no regressions on the others. None of the four require omni#127 to be merged first — this PR stands on the released omni.Changes
toArray()— no-op cursor terminatorJoins
pretty()intranslateCursorMethodas a no-op. Cursors are already materialized intoResult.Value; the chained call is purely cosmetic in mongo shell. Covers wild shapedb.coll.find({…}).limit(N).toArray().db.runCommand({…})— generic escape hatchAdds
OpRunCommand, anextractRunCommandArgsvalidator (1 doc, non-empty), and anexecuteRunCommandthin wrapper that hands the body tomongo.Database.RunCommandand returns thebson.Dresponse unchanged. Result-shape doc inclient.goupdated.explain()on read ops + aggregate{explain: true}optionA trailing
.explain()/.explain(\"verbosity\")onfind/aggregate/count/distinctrewrites the operation into{explain: <innerCommand>, verbosity: <v>}and runs it asOpRunCommand. The aggregate-option formdb.coll.aggregate([…], {explain: true})routes to the same code path. Verbosity defaults to\"queryPlanner\"and is validated against the three accepted values. Write-op explain (update / delete) is intentionally out of scope.Empty / comment-only input — no-op
Pure-comment statements (e.g.,
// db.coll.update(...)) previously errored withempty statement: …. mongosh treats them as no-op successes; gomongo now does too via a newOpNoOpreturned by the translator and handled by the executor as an emptyResult.Test plan
TestToArrayNoOpcovers the four wild shapes (find, find+limit, find+filter+projection, aggregate)TestRunCommandcoversping, the legacycountcommand (the wild case), empty body rejection, wrong arity rejectionTestExplaincovers post-method on find / aggregate / count(+filter) / count() / distinct, both verbosities, the aggregate-option form, plus invalid verbosity / wrong arity / explain on unsupported op typeTestCommentOnlyStatementIsNoOpcovers line comments, multi-line comments, block comments, whitespace-only, empty string, and comment-followed-by-real-statement🤖 Generated with Claude Code