Skip to content

RUBY-3213 Add Database#cursor_command to create a cursor from a command response#3055

Open
comandeo-mongo wants to merge 1 commit into
mongodb:masterfrom
comandeo-mongo:3213-cursor-command
Open

RUBY-3213 Add Database#cursor_command to create a cursor from a command response#3055
comandeo-mongo wants to merge 1 commit into
mongodb:masterfrom
comandeo-mongo:3213-cursor-command

Conversation

@comandeo-mongo

Copy link
Copy Markdown
Contributor

Implements the runCursorCommand API from the run-command spec as Mongo::Database#cursor_command.

The method runs a cursor-returning command and parses the response into a Mongo::Cursor. The user's command is sent unmodified. If the response does not contain a cursor field, Mongo::Error::InvalidCursorOperation is raised. The implicit session is created if none is provided, reused across all getMores, and ended when the cursor is exhausted or closed. getMore commands can be configured with batch_size, max_time_ms, and comment. The command is never retried, per the spec.

Changes

Implementation:

  • lib/mongo/database.rb — new public #cursor_command and a private options helper.
  • lib/mongo/database/cursor_command_view.rb — minimal view the Cursor reads from when built from a command response (carries getMore options, cursor type, timeout mode).
  • lib/mongo/operation/cursor_command.rb (+ op_msg.rb, result.rb) — the operation and a result that parses the cursor subdocument (firstBatch, ns, id).
  • lib/mongo/operation.rb — require the new operation.
  • lib/mongo/cursor.rbgetMore now sends a getMore-specific maxTimeMS, routed via a uniquely named view method guarded by respond_to? so find/aggregate cursors are unaffected.

Tests:

  • spec/spec_tests/data/run_command_unified/runCursorCommand.yml — unified fixtures from the spec.
  • spec/runners/unified/support_operations.rbrunCursorCommand and createCommandCursor operations.
  • spec/runners/unified/ddl_operations.rbcreateCollection now honors saveResultAsEntity (needed by the tailable fixture).

Test plan

Run against a local replica set:

  • bundle exec rspec spec/spec_tests/run_command_unified_spec.rb — 21 examples, 0 failures, 4 pending. The pending cases require sharded (checkMetadataConsistency) and load-balanced topologies, which the local replica set cannot provide; they run in Evergreen.
  • bundle exec rspec spec/mongo/cursor_spec.rb — 42 examples, 0 failures (no regression from the get_more_operation change).
  • bundle exec rspec spec/mongo/database_spec.rb — 112 examples, 0 failures.
  • bundle exec rspec spec/integration/cursor_reaping_spec.rb spec/integration/get_more_spec.rb spec/mongo/collection/view_spec.rb — 0 failures.
  • bundle exec rubocop on all changed files — no offenses.

Jira: https://jira.mongodb.org/browse/RUBY-3213

…nd response

Implements the runCursorCommand API from the run-command spec. The command
is run unmodified and its response is parsed as a cursor; if the response has
no cursor field an Error::InvalidCursorOperation is raised. The implicit
session is reused across getMores and ended when the cursor is exhausted or
closed. getMore commands can be configured with batch_size, max_time_ms and
comment options. The command is never retried.

Adds the runCursorCommand unified test fixtures and the runCursorCommand /
createCommandCursor operations to the unified test runner.
@comandeo-mongo comandeo-mongo added the feature A PR for a new feature label Jun 9, 2026
@comandeo-mongo comandeo-mongo marked this pull request as ready for review June 10, 2026 11:22
@comandeo-mongo comandeo-mongo requested a review from a team as a code owner June 10, 2026 11:22
@comandeo-mongo comandeo-mongo requested review from Copilot and jamis June 10, 2026 11:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for the run-command spec’s runCursorCommand API by introducing a new public Mongo::Database#cursor_command method that executes an arbitrary cursor-returning command and materializes the response into a Mongo::Cursor, including configurable getMore options.

Changes:

  • Added Mongo::Database#cursor_command, a minimal CursorCommandView, and a new Operation::CursorCommand (+ result parsing) to build cursors from command responses.
  • Updated Mongo::Cursor#get_more_operation to optionally send a getMore-specific maxTimeMS when the backing view supports it (leaving existing find/aggregate cursors unchanged).
  • Added unified spec fixtures and runner support operations for runCursorCommand / createCommandCursor, plus a unified DDL runner tweak to support entity saving for a capped collection fixture.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
spec/spec_tests/data/run_command_unified/runCursorCommand.yml Adds unified spec fixtures covering cursor-command behavior (sessions, LB pinning, getMore options, tailable).
spec/runners/unified/support_operations.rb Implements unified runner operations to execute runCursorCommand and create/save command cursors as entities.
spec/runners/unified/ddl_operations.rb Updates createCollection unified operation to honor saveResultAsEntity.
lib/mongo/operation/cursor_command/result.rb Adds a result wrapper that extracts cursor subdocument fields for cursor construction.
lib/mongo/operation/cursor_command/op_msg.rb Defines OP_MSG command execution for cursor commands, sending selector verbatim.
lib/mongo/operation/cursor_command.rb Introduces the CursorCommand operation type.
lib/mongo/operation.rb Requires the new cursor command operation.
lib/mongo/database/cursor_command_view.rb Adds a minimal view object used by cursors created from command responses.
lib/mongo/database.rb Adds Database#cursor_command implementation and view-option extraction helper.
lib/mongo/cursor.rb Allows getMore to include a getMore-specific maxTimeMS when exposed by the view.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/mongo/database.rb
Comment on lines +331 to +338
server = selector.select_server(cluster, nil, session)
result = if server.load_balancer?
# The connection is checked in by the cursor when it is drained.
connection = server.pool.check_out(context: context)
op.execute_with_connection(connection, context: context, options: execution_opts)
else
op.execute(server, context: context, options: execution_opts)
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unfamiliar with the spec, here, but this does seem like a legitimate omission. Should this be wrapped in with_overload_retry? Collection::View::Iterable, for example, uses with_overload_retry when sending the initial cursor command.

Comment thread lib/mongo/database.rb
Comment on lines +351 to +354
unless cursor
connection.connection_pool.check_in(connection) if connection && !connection.pinned?
session.end_session if session && session.implicit?
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sensitive to the risk of leaking connections (I think I'm kind of traumatized by that help ticket I worked on recently...!) so comment definitely catches my attention. I'm not entirely sure that Copilot is proposing the right solution here, though -- assuming something failed during Cursor.new, would the connection have been pinned?

Comment on lines +321 to +323
runOnRequirements:
- serverless: forbid
operations:

@jamis jamis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving this, but wanted to flag two of the issues that Copilot raised. I'm not familiar enough with the spec, here, to know if they actually have merit.

Comment thread lib/mongo/database.rb
Comment on lines +331 to +338
server = selector.select_server(cluster, nil, session)
result = if server.load_balancer?
# The connection is checked in by the cursor when it is drained.
connection = server.pool.check_out(context: context)
op.execute_with_connection(connection, context: context, options: execution_opts)
else
op.execute(server, context: context, options: execution_opts)
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unfamiliar with the spec, here, but this does seem like a legitimate omission. Should this be wrapped in with_overload_retry? Collection::View::Iterable, for example, uses with_overload_retry when sending the initial cursor command.

Comment thread lib/mongo/database.rb
Comment on lines +351 to +354
unless cursor
connection.connection_pool.check_in(connection) if connection && !connection.pinned?
session.end_session if session && session.implicit?
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sensitive to the risk of leaking connections (I think I'm kind of traumatized by that help ticket I worked on recently...!) so comment definitely catches my attention. I'm not entirely sure that Copilot is proposing the right solution here, though -- assuming something failed during Cursor.new, would the connection have been pinned?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature A PR for a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants