Class: Mongo::Cursor Private
- Inherits:
-
Object
- Object
- Mongo::Cursor
- Extended by:
- Forwardable
- Includes:
- Enumerable, Retryable
- Defined in:
- lib/mongo/cursor.rb,
lib/mongo/cursor/kill_spec.rb,
lib/mongo/cursor/nontailable.rb
Overview
This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.
Client-side representation of an iterator over a query result set on the server.
Cursor objects are not directly exposed to application code. Rather, Collection::View exposes the Enumerable interface to the applications, and the enumerator is backed by a Cursor instance.
Direct Known Subclasses
Defined Under Namespace
Modules: NonTailable Classes: KillSpec
Instance Attribute Summary collapse
- #connection ⇒ Object readonly private
-
#context ⇒ Operation::Context
readonly
private
Context the context for this cursor.
- #initial_result ⇒ Object readonly private
-
#resume_token ⇒ BSON::Document | nil
readonly
private
The resume token tracked by the cursor for change stream resuming.
- #server ⇒ Object readonly private
-
#view ⇒ Collection::View
readonly
private
View The collection view.
Class Method Summary collapse
-
.finalize(kill_spec, cluster) ⇒ Proc
private
Finalize the cursor for garbage collection.
Instance Method Summary collapse
-
#batch_size ⇒ Integer
private
Get the batch size.
-
#close(opts = {}) ⇒ nil
private
Closes this cursor, freeing any associated resources on the client and the server.
-
#closed? ⇒ true, false
private
Is the cursor closed?.
-
#collection_name ⇒ String
private
Get the parsed collection name.
-
#each ⇒ Enumerator
private
Iterate through documents returned from the query.
- #fully_iterated? ⇒ Boolean private
-
#get_more ⇒ Array<BSON::Document>
private
Execute a getMore command and return the batch of documents obtained from the server.
-
#id ⇒ Integer
private
Get the cursor id.
-
#initialize(view, result, server, options = {}) ⇒ Cursor
constructor
private
Creates a
Cursorobject. -
#inspect ⇒ String
private
Get a human-readable string representation of
Cursor. - #kill_spec(connection_global_id) ⇒ Object private
-
#refresh_timeout! ⇒ Object
private
Refreshes the cursor’s CSOT context so that the next getMore starts with a fresh timeout deadline.
-
#try_next ⇒ BSON::Document | nil
private
Return one document from the query, if one is available.
Methods included from Retryable
#read_worker, #select_server, #with_overload_retry, #write_worker
Constructor Details
#initialize(view, result, server, options = {}) ⇒ Cursor
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Creates a Cursor object.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/mongo/cursor.rb', line 72 def initialize(view, result, server, = {}) unless result.is_a?(Operation::Result) raise ArgumentError, "Second argument must be a Mongo::Operation::Result: #{result.inspect}" end @view = view @server = server @initial_result = result @namespace = result.namespace @remaining = limit if limited? set_cursor_id(result) raise ArgumentError, 'Cursor id must be present in the result' if @cursor_id.nil? @options = @session = @options[:session] @connection_global_id = result.connection_global_id @context = @options[:context]&.with(connection_global_id: connection_global_id_for_context) || fresh_context @explicitly_closed = false @get_more_network_error = false @lock = Mutex.new if server.load_balancer? # We need the connection in the cursor only in load balanced topology; # we do not need an additional reference to it otherwise. @connection = @initial_result.connection end if closed? check_in_connection else register ObjectSpace.define_finalizer( self, self.class.finalize(kill_spec(@connection_global_id), cluster) ) end end |
Instance Attribute Details
#connection ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
115 116 117 |
# File 'lib/mongo/cursor.rb', line 115 def connection @connection end |
#context ⇒ Operation::Context (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns context the context for this cursor.
51 52 53 |
# File 'lib/mongo/cursor.rb', line 51 def context @context end |
#initial_result ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
112 113 114 |
# File 'lib/mongo/cursor.rb', line 112 def initial_result @initial_result end |
#resume_token ⇒ BSON::Document | nil (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The resume token tracked by the cursor for change stream resuming
48 49 50 |
# File 'lib/mongo/cursor.rb', line 48 def resume_token @resume_token end |
#server ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
109 110 111 |
# File 'lib/mongo/cursor.rb', line 109 def server @server end |
#view ⇒ Collection::View (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns view The collection view.
42 43 44 |
# File 'lib/mongo/cursor.rb', line 42 def view @view end |
Class Method Details
.finalize(kill_spec, cluster) ⇒ Proc
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Finalize the cursor for garbage collection. Schedules this cursor to be included in a killCursors operation executed by the Cluster’s CursorReaper.
126 127 128 129 130 131 132 |
# File 'lib/mongo/cursor.rb', line 126 def self.finalize(kill_spec, cluster) raise ArgumentError, "First argument must be a KillSpec: #{kill_spec.inspect}" unless kill_spec.is_a?(KillSpec) proc do cluster.schedule_kill_cursor(kill_spec) end end |
Instance Method Details
#batch_size ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Get the batch size.
269 270 271 272 273 274 275 276 |
# File 'lib/mongo/cursor.rb', line 269 def batch_size value = (@view.batch_size && @view.batch_size > 0) ? @view.batch_size : limit if value == 0 nil else value end end |
#close(opts = {}) ⇒ nil
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Closes this cursor, freeing any associated resources on the client and the server.
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/mongo/cursor.rb', line 295 def close(opts = {}) return if closed? ctx = context ? context.refresh(timeout_ms: opts[:timeout_ms]) : fresh_context(opts) unregister unless @get_more_network_error read_with_one_retry do spec = { coll_name: collection_name, db_name: database.name, cursor_ids: [ id ], } op = Operation::KillCursors.new(spec) execute_operation(op, context: ctx) end end nil rescue Error::OperationFailure::Family, Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished # Errors are swallowed since there is nothing can be done by handling them. ensure end_session @cursor_id = 0 @lock.synchronize do @explicitly_closed = true end check_in_connection end |
#closed? ⇒ true, false
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Is the cursor closed?
286 287 288 289 |
# File 'lib/mongo/cursor.rb', line 286 def closed? # @cursor_id should in principle never be nil @cursor_id.nil? || @cursor_id == 0 end |
#collection_name ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Get the parsed collection name.
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/mongo/cursor.rb', line 333 def collection_name # In most cases, this will be equivalent to the name of the collection # object in the driver. However, in some cases (e.g. when connected # to an Atlas Data Lake), the namespace returned by the find command # may be different, which is why we want to use the collection name based # on the namespace in the command result. if @namespace # Often, the namespace will be in the format "database.collection". # However, sometimes the collection name will contain periods, which # is why this method joins all the namespace components after the first. ns_components = @namespace.split('.') ns_components[1...ns_components.length].join('.') else collection.name end end |
#each ⇒ Enumerator
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Iterate through documents returned from the query.
A cursor may be iterated at most once. Incomplete iteration is also allowed. Attempting to iterate the cursor more than once raises InvalidCursorOperation.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/mongo/cursor.rb', line 160 def each # If we already iterated past the first batch (i.e., called get_more # at least once), the cursor on the server side has advanced past # the first batch and restarting iteration from the beginning by # returning initial result would miss documents in the second batch # and subsequent batches up to wherever the cursor is. Detect this # condition and abort the iteration. # # In a future driver version, each would either continue from the # end of previous iteration or would always restart from the # beginning. if @get_more_called raise Error::InvalidCursorOperation, 'Cannot restart iteration of a cursor which issued a getMore' end # To maintain compatibility with pre-2.10 driver versions, reset # the documents array each time a new iteration is started. @documents = nil if block_given? # StopIteration raised by try_next ends this loop. loop do document = try_next raise Error::InvalidCursorOperation, 'Cursor was explicitly closed' if explicitly_closed? yield document if document end self else documents = [] # StopIteration raised by try_next ends this loop. loop do document = try_next raise Error::InvalidCursorOperation, 'Cursor was explicitly closed' if explicitly_closed? documents << document if document end documents end end |
#fully_iterated? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
409 410 411 |
# File 'lib/mongo/cursor.rb', line 409 def fully_iterated? !!@fully_iterated end |
#get_more ⇒ Array<BSON::Document>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Execute a getMore command and return the batch of documents obtained from the server.
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
# File 'lib/mongo/cursor.rb', line 370 def get_more @get_more_called = true # Modern retryable reads specification prohibits retrying getMores. # Legacy retryable read logic used to retry getMores, but since # doing so may result in silent data loss, the driver no longer retries # getMore operations in any circumstance. # https://github.com/mongodb/specifications/blob/master/source/retryable-reads/retryable-reads.md#qa # # However, overload errors (SystemOverloadedError + RetryableError) are # retried with exponential backoff since the server never processed # the request. with_overload_retry(context: possibly_refreshed_context) do process(execute_operation(get_more_operation)) end rescue Error::SocketError, Error::SocketTimeoutError @get_more_network_error = true raise rescue Error::OperationFailure => e # When overload retries are exhausted on getMore, close the cursor # so that killCursors is sent to the server. close if e.label?('RetryableError') && e.label?('SystemOverloadedError') raise end |
#id ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
A cursor id of 0 means the cursor was closed on the server.
Get the cursor id.
360 361 362 |
# File 'lib/mongo/cursor.rb', line 360 def id @cursor_id end |
#inspect ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Get a human-readable string representation of Cursor.
142 143 144 |
# File 'lib/mongo/cursor.rb', line 142 def inspect "#<Mongo::Cursor:0x#{object_id} @view=#{@view.inspect}>" end |
#kill_spec(connection_global_id) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
396 397 398 399 400 401 402 403 404 405 406 |
# File 'lib/mongo/cursor.rb', line 396 def kill_spec(connection_global_id) KillSpec.new( cursor_id: id, coll_name: collection_name, db_name: database.name, connection_global_id: connection_global_id, server_address: server.address, session: @session, connection: @connection ) end |
#refresh_timeout! ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Refreshes the cursor’s CSOT context so that the next getMore starts with a fresh timeout deadline. Used by tailable awaitData cursors to implement per-iteration timeout refresh as required by the CSOT spec. Only refreshes if this is a tailable awaitData cursor with an active timeout.
419 420 421 422 423 |
# File 'lib/mongo/cursor.rb', line 419 def refresh_timeout! return unless view.cursor_type == :tailable_await && context.timeout? @context = @context.refresh(view: view) end |
#try_next ⇒ BSON::Document | nil
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method is experimental and subject to change.
Return one document from the query, if one is available.
This method will wait up to max_await_time_ms milliseconds for changes from the server, and if no changes are received it will return nil. If there are no more documents to return from the server, or if we have exhausted the cursor, it will raise a StopIteration exception.
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/mongo/cursor.rb', line 217 def try_next if @documents.nil? # Since published versions of Mongoid have a copy of old driver cursor # code, our dup call in #process isn't invoked when Mongoid query # cache is active. Work around that by also calling dup here on # the result of #process which might come out of Mongoid's code. @documents = process(@initial_result).dup # the documents here can be an empty array, hence # we may end up issuing a getMore in the first try_next call end if @documents.empty? # On empty batches, we cache the batch resume token cache_batch_resume_token if closed? @fully_iterated = true raise StopIteration else if exhausted? close @fully_iterated = true raise StopIteration end @documents = get_more end else # cursor is closed here # keep documents as an empty array end # If there is at least one document, cache its _id cache_resume_token(@documents[0]) if @documents[0] # Cache the batch resume token if we are iterating # over the last document, or if the batch is empty if @documents.size <= 1 cache_batch_resume_token @fully_iterated = true if closed? end @documents.shift end |