mongo 2.24.1 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mongo_console +2 -2
  3. data/lib/mongo/address/validator.rb +1 -1
  4. data/lib/mongo/address.rb +1 -3
  5. data/lib/mongo/auth/aws/conversation.rb +0 -4
  6. data/lib/mongo/auth/aws/credentials_retriever.rb +3 -8
  7. data/lib/mongo/auth/base.rb +1 -1
  8. data/lib/mongo/auth/sasl_conversation_base.rb +1 -1
  9. data/lib/mongo/auth/scram_conversation_base.rb +1 -8
  10. data/lib/mongo/auth/stringprep.rb +1 -6
  11. data/lib/mongo/auth/user/view.rb +1 -1
  12. data/lib/mongo/bulk_write/result_combiner.rb +3 -3
  13. data/lib/mongo/bulk_write.rb +1 -1
  14. data/lib/mongo/client.rb +82 -12
  15. data/lib/mongo/cluster/reapers/cursor_reaper.rb +12 -5
  16. data/lib/mongo/cluster/sdam_flow.rb +4 -4
  17. data/lib/mongo/cluster/topology/base.rb +1 -1
  18. data/lib/mongo/cluster.rb +12 -28
  19. data/lib/mongo/collection/view/aggregation/behavior.rb +1 -1
  20. data/lib/mongo/collection/view/aggregation.rb +2 -4
  21. data/lib/mongo/collection/view/builder/aggregation.rb +3 -5
  22. data/lib/mongo/collection/view/builder/map_reduce.rb +1 -1
  23. data/lib/mongo/collection/view/change_stream.rb +14 -9
  24. data/lib/mongo/collection/view/map_reduce.rb +2 -11
  25. data/lib/mongo/collection/view/readable.rb +11 -7
  26. data/lib/mongo/collection/view/writable.rb +4 -4
  27. data/lib/mongo/collection/view.rb +8 -4
  28. data/lib/mongo/collection.rb +1 -1
  29. data/lib/mongo/crypt/auto_encrypter.rb +1 -1
  30. data/lib/mongo/crypt/binary.rb +7 -4
  31. data/lib/mongo/crypt/binding.rb +88 -10
  32. data/lib/mongo/crypt/context.rb +50 -19
  33. data/lib/mongo/crypt/encryption_io.rb +11 -5
  34. data/lib/mongo/crypt/handle.rb +17 -5
  35. data/lib/mongo/crypt/kms/credentials.rb +70 -31
  36. data/lib/mongo/crypt/kms/kmip/master_document.rb +30 -1
  37. data/lib/mongo/crypt/kms/master_key_document.rb +11 -6
  38. data/lib/mongo/crypt/kms.rb +12 -1
  39. data/lib/mongo/cursor.rb +8 -2
  40. data/lib/mongo/database/cursor_command_view.rb +95 -0
  41. data/lib/mongo/database.rb +144 -18
  42. data/lib/mongo/error/invalid_uri.rb +5 -1
  43. data/lib/mongo/error/operation_failure.rb +2 -2
  44. data/lib/mongo/error/parser.rb +3 -3
  45. data/lib/mongo/error/sdam_error_detection.rb +2 -3
  46. data/lib/mongo/grid/fs_bucket.rb +27 -21
  47. data/lib/mongo/grid/stream/write.rb +2 -2
  48. data/lib/mongo/index/view.rb +13 -7
  49. data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +1 -0
  50. data/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb +1 -0
  51. data/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +1 -0
  52. data/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +1 -0
  53. data/lib/mongo/monitoring/event/cmap/connection_closed.rb +1 -0
  54. data/lib/mongo/monitoring/event/cmap/connection_created.rb +1 -0
  55. data/lib/mongo/monitoring/event/cmap/connection_ready.rb +1 -0
  56. data/lib/mongo/monitoring/event/cmap/pool_cleared.rb +1 -0
  57. data/lib/mongo/monitoring/event/cmap/pool_closed.rb +1 -0
  58. data/lib/mongo/monitoring/event/cmap/pool_created.rb +1 -0
  59. data/lib/mongo/monitoring/event/cmap/pool_ready.rb +1 -0
  60. data/lib/mongo/monitoring/event/command_failed.rb +1 -0
  61. data/lib/mongo/monitoring/event/command_started.rb +1 -0
  62. data/lib/mongo/monitoring/event/command_succeeded.rb +1 -0
  63. data/lib/mongo/monitoring/event/secure.rb +12 -17
  64. data/lib/mongo/monitoring/event/server_closed.rb +1 -0
  65. data/lib/mongo/monitoring/event/server_description_changed.rb +1 -0
  66. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +1 -0
  67. data/lib/mongo/monitoring/event/server_heartbeat_started.rb +1 -0
  68. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +1 -0
  69. data/lib/mongo/monitoring/event/server_opening.rb +1 -0
  70. data/lib/mongo/monitoring/event/topology_changed.rb +1 -0
  71. data/lib/mongo/monitoring/event/topology_closed.rb +1 -0
  72. data/lib/mongo/monitoring/event/topology_opening.rb +1 -0
  73. data/lib/mongo/operation/create/op_msg.rb +1 -0
  74. data/lib/mongo/operation/create_index/op_msg.rb +2 -1
  75. data/lib/mongo/operation/cursor_command/op_msg.rb +37 -0
  76. data/lib/mongo/operation/cursor_command/result.rb +60 -0
  77. data/lib/mongo/operation/cursor_command.rb +33 -0
  78. data/lib/mongo/operation/delete/op_msg.rb +1 -0
  79. data/lib/mongo/operation/drop/op_msg.rb +1 -0
  80. data/lib/mongo/operation/drop_database/op_msg.rb +1 -0
  81. data/lib/mongo/operation/drop_index/op_msg.rb +1 -0
  82. data/lib/mongo/operation/find/builder/command.rb +1 -1
  83. data/lib/mongo/operation/insert/bulk_result.rb +2 -4
  84. data/lib/mongo/operation/insert/op_msg.rb +1 -0
  85. data/lib/mongo/operation/result.rb +12 -25
  86. data/lib/mongo/operation/shared/causal_consistency_supported.rb +4 -2
  87. data/lib/mongo/operation/shared/executable.rb +1 -9
  88. data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
  89. data/lib/mongo/operation/shared/sessions_supported.rb +3 -5
  90. data/lib/mongo/operation/update/bulk_result.rb +1 -1
  91. data/lib/mongo/operation/update/op_msg.rb +1 -0
  92. data/lib/mongo/operation/write_command/op_msg.rb +2 -0
  93. data/lib/mongo/operation.rb +1 -0
  94. data/lib/mongo/options/redacted.rb +2 -2
  95. data/lib/mongo/protocol/compressed.rb +15 -11
  96. data/lib/mongo/protocol/message.rb +16 -16
  97. data/lib/mongo/protocol/registry.rb +1 -1
  98. data/lib/mongo/protocol/serializers.rb +1 -3
  99. data/lib/mongo/query_cache.rb +3 -5
  100. data/lib/mongo/server/connection.rb +3 -1
  101. data/lib/mongo/server/connection_common.rb +9 -1
  102. data/lib/mongo/server/connection_pool/generation_manager.rb +1 -1
  103. data/lib/mongo/server/connection_pool.rb +24 -10
  104. data/lib/mongo/server/description/features.rb +1 -1
  105. data/lib/mongo/server/description.rb +1 -1
  106. data/lib/mongo/server/monitor/connection.rb +3 -2
  107. data/lib/mongo/server/monitor.rb +133 -30
  108. data/lib/mongo/server/pending_connection.rb +1 -0
  109. data/lib/mongo/server/push_monitor.rb +16 -2
  110. data/lib/mongo/server.rb +27 -7
  111. data/lib/mongo/server_selector/base.rb +3 -6
  112. data/lib/mongo/server_selector/secondary_preferred.rb +4 -1
  113. data/lib/mongo/session.rb +22 -23
  114. data/lib/mongo/socket/ssl.rb +3 -3
  115. data/lib/mongo/socket.rb +1 -7
  116. data/lib/mongo/srv/monitor.rb +11 -2
  117. data/lib/mongo/srv/resolver.rb +1 -1
  118. data/lib/mongo/srv/result.rb +5 -3
  119. data/lib/mongo/timeout.rb +10 -15
  120. data/lib/mongo/tracing/open_telemetry/command_tracer.rb +16 -2
  121. data/lib/mongo/uri/options_mapper.rb +21 -9
  122. data/lib/mongo/uri/srv_protocol.rb +4 -4
  123. data/lib/mongo/uri.rb +56 -8
  124. data/lib/mongo/utils.rb +1 -1
  125. data/lib/mongo/version.rb +1 -1
  126. metadata +6 -4
  127. data/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +0 -173
  128. data/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +0 -1164
@@ -22,23 +22,28 @@ module Mongo
22
22
  #
23
23
  # @api private
24
24
  class MasterKeyDocument
25
- # Known KMS provider names.
25
+ # Known KMS provider types.
26
26
  KMS_PROVIDERS = %w[aws azure gcp kmip local].freeze
27
27
 
28
- # Creates a master key document object form a parameters hash.
28
+ # Creates a master key document object from a parameters hash.
29
29
  #
30
- # @param [ String ] kms_provider. KMS provider name.
30
+ # @param [ String ] kms_provider KMS provider identifier. May be a
31
+ # provider type (e.g. "aws") or a named provider (e.g. "aws:name1").
31
32
  # @param [ Hash ] options A hash that contains master key options for
32
33
  # the KMS provider.
33
34
  # Required parameters for KMS providers are described in corresponding
34
35
  # classes inside Mongo::Crypt::KMS module.
35
36
  #
36
- # @raise [ ArgumentError ] If required options are missing or incorrectly.
37
+ # @raise [ ArgumentError ] If required options are missing or incorrectly
38
+ # formatted.
37
39
  def initialize(kms_provider, options)
38
40
  raise ArgumentError.new('Key document options must not be nil') if options.nil?
39
41
 
42
+ @provider = kms_provider.to_s
43
+ provider_type = KMS.provider_base_type(@provider)
44
+
40
45
  master_key = options.fetch(:master_key, {})
41
- @key_document = case kms_provider.to_s
46
+ @key_document = case provider_type
42
47
  when 'aws' then KMS::AWS::MasterKeyDocument.new(master_key)
43
48
  when 'azure' then KMS::Azure::MasterKeyDocument.new(master_key)
44
49
  when 'gcp' then KMS::GCP::MasterKeyDocument.new(master_key)
@@ -53,7 +58,7 @@ module Mongo
53
58
  #
54
59
  # @return [ BSON::Document ] Master key document as BSON document.
55
60
  def to_document
56
- @key_document.to_document
61
+ @key_document.to_document.merge(provider: @provider)
57
62
  end
58
63
  end
59
64
  end
@@ -16,13 +16,24 @@
16
16
 
17
17
  module Mongo
18
18
  module Crypt
19
- module KMS
19
+ module KMS # rubocop:disable Style/Documentation
20
20
  # This error indicates that we could not obtain credential for
21
21
  # a KMS service.
22
22
  #
23
23
  # @api private
24
24
  class CredentialsNotFound < RuntimeError; end
25
25
 
26
+ # Returns the base provider type from a KMS provider identifier.
27
+ # For example, "aws:name1" returns "aws" and :aws returns "aws".
28
+ #
29
+ # @param [ String | Symbol ] identifier The KMS provider identifier.
30
+ # @return [ String ] The base provider type.
31
+ #
32
+ # @api private
33
+ def self.provider_base_type(identifier)
34
+ identifier.to_s.split(':').first
35
+ end
36
+
26
37
  # This module contains helper methods for validating KMS parameters.
27
38
  #
28
39
  # @api private
data/lib/mongo/cursor.rb CHANGED
@@ -382,7 +382,7 @@ module Mongo
382
382
  with_overload_retry(context: possibly_refreshed_context) do
383
383
  process(execute_operation(get_more_operation))
384
384
  end
385
- rescue Error::SocketError, Error::SocketTimeoutError
385
+ rescue Error::SocketError, Error::SocketTimeoutError, Error::ConnectionPerished
386
386
  @get_more_network_error = true
387
387
  raise
388
388
  rescue Error::OperationFailure => e
@@ -463,6 +463,12 @@ module Mongo
463
463
  if view.respond_to?(:options) && view.options.is_a?(Hash) && !view.options[:comment].nil?
464
464
  spec[:comment] = view.options[:comment]
465
465
  end
466
+ # A cursor built from a runCursorCommand response carries a getMore-specific
467
+ # maxTimeMS that is sent on getMore commands. Regular find/aggregate views
468
+ # do not expose this, so their getMores are unaffected.
469
+ if view.respond_to?(:max_time_ms_for_get_more) && view.max_time_ms_for_get_more
470
+ spec[:max_time_ms] = view.max_time_ms_for_get_more
471
+ end
466
472
  Operation::GetMore.new(spec)
467
473
  end
468
474
 
@@ -501,7 +507,7 @@ module Mongo
501
507
  end
502
508
 
503
509
  def limit
504
- @view.send(:limit)
510
+ @view.limit
505
511
  end
506
512
 
507
513
  def register
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2025 MongoDB Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Mongo
18
+ class Database
19
+ # The minimal view a Cursor needs when it is built from an arbitrary
20
+ # command response rather than from a collection query.
21
+ #
22
+ # It carries the getMore-specific options (batchSize, maxTimeMS, comment)
23
+ # and the cursor type and timeout mode, and answers the few methods the
24
+ # Cursor reads from its view.
25
+ #
26
+ # @api private
27
+ class CursorCommandView
28
+ # @param [ Mongo::Database ] database The database the command ran on.
29
+ # @param [ Hash ] options The getMore and timeout options.
30
+ #
31
+ # @option options [ Integer ] :batch_size The batchSize for getMores.
32
+ # @option options [ Integer ] :max_time_ms The maxTimeMS for getMores.
33
+ # @option options [ Object ] :comment The comment for getMores.
34
+ # @option options [ Symbol ] :cursor_type :tailable or :tailable_await.
35
+ # @option options [ Symbol ] :timeout_mode :cursor_lifetime or :iteration.
36
+ def initialize(database, options = {})
37
+ @database = database
38
+ @options = options
39
+ end
40
+
41
+ # @return [ Mongo::Database ] The database the command ran on.
42
+ attr_reader :database
43
+
44
+ # @return [ Hash ] The view options. Used by the Cursor to read the
45
+ # getMore comment.
46
+ attr_reader :options
47
+
48
+ # @return [ Mongo::Client ] The client.
49
+ def client
50
+ database.client
51
+ end
52
+
53
+ # A placeholder collection used only so the Cursor can reach the client
54
+ # and database. The actual namespace for getMore and killCursors is taken
55
+ # from the command response, not from this collection.
56
+ #
57
+ # @return [ Mongo::Collection ] The $cmd pseudo collection.
58
+ def collection
59
+ @collection ||= Collection.new(database, '$cmd')
60
+ end
61
+
62
+ # @return [ Integer | nil ] The batchSize sent on getMore commands.
63
+ def batch_size
64
+ options[:batch_size]
65
+ end
66
+
67
+ # @return [ Integer | nil ] The maxTimeMS sent on getMore commands.
68
+ def max_time_ms_for_get_more
69
+ options[:max_time_ms]
70
+ end
71
+
72
+ # @return [ Symbol | nil ] The cursor type.
73
+ def cursor_type
74
+ options[:cursor_type]
75
+ end
76
+
77
+ # @return [ Symbol | nil ] The timeout mode.
78
+ def timeout_mode
79
+ options[:timeout_mode]
80
+ end
81
+
82
+ # @return [ Hash ] timeout values for the operation context.
83
+ def operation_timeouts(opts = {})
84
+ database.operation_timeouts(opts)
85
+ end
86
+
87
+ # Cursors do not support a limit when built from a command response.
88
+ #
89
+ # @api private
90
+ def limit
91
+ nil
92
+ end
93
+ end
94
+ end
95
+ end
@@ -15,6 +15,7 @@
15
15
  # limitations under the License.
16
16
 
17
17
  require 'mongo/database/view'
18
+ require 'mongo/database/cursor_command_view'
18
19
 
19
20
  module Mongo
20
21
  # Represents a database on the db server and operations that can execute on
@@ -259,6 +260,119 @@ module Mongo
259
260
  end
260
261
  end
261
262
 
263
+ # Run a command that returns a cursor and parse the response as a cursor.
264
+ #
265
+ # The command is sent to the server unmodified; the driver MUST NOT inspect
266
+ # or alter it. If the response does not contain a cursor field an error is
267
+ # raised. The command is never retried.
268
+ #
269
+ # Note: if a +maxTimeMS+ field is already set on the command document it is
270
+ # left as-is. The +max_time_ms+ option below applies only to getMore
271
+ # commands. Setting both +timeout_ms+ and +max_time_ms+ is not supported
272
+ # and has undefined behavior.
273
+ #
274
+ # @example Run a cursor-returning command.
275
+ # database.cursor_command(checkMetadataConsistency: 1)
276
+ #
277
+ # @param [ Hash ] command The command to execute.
278
+ # @param [ Hash ] options The command options.
279
+ #
280
+ # @option options [ Hash ] :read The read preference for this command,
281
+ # used for server selection and reused for subsequent getMores.
282
+ # @option options [ Session ] :session The session to use. If none is
283
+ # given an implicit session is created and reused for the cursor's
284
+ # lifetime.
285
+ # @option options [ Integer ] :timeout_ms The operation timeout in
286
+ # milliseconds.
287
+ # @option options [ Integer ] :batch_size The batchSize to send on getMore
288
+ # commands.
289
+ # @option options [ Integer ] :max_time_ms The maxTimeMS to send on getMore
290
+ # commands.
291
+ # @option options [ Object ] :comment A comment to attach to getMore
292
+ # commands.
293
+ # @option options [ Symbol ] :cursor_type The cursor type, :tailable or
294
+ # :tailable_await. Must match the flags set on the command document.
295
+ # @option options [ Symbol ] :timeout_mode :cursor_lifetime or :iteration.
296
+ #
297
+ # @return [ Mongo::Cursor ] A cursor over the command results.
298
+ #
299
+ # @raise [ Error::InvalidCursorOperation ] If the response does not contain
300
+ # a cursor.
301
+ def cursor_command(command, options = {})
302
+ options = options.dup
303
+ execution_opts = options.delete(:execution_options) || {}
304
+ view_options = extract_cursor_command_view_options(options)
305
+
306
+ txn_read_pref = (options[:session].txn_read_preference if options[:session] && options[:session].in_transaction?)
307
+ txn_read_pref ||= options[:read] || ServerSelector::PRIMARY
308
+ Lint.validate_underscore_read_preference(txn_read_pref)
309
+ selector = ServerSelector.get(txn_read_pref)
310
+
311
+ # The session is intentionally not wrapped in #with_session: an implicit
312
+ # session must outlive this method and is ended by the cursor when it is
313
+ # exhausted or closed. Until the cursor takes ownership, the session and
314
+ # any load-balanced connection are cleaned up here on every exit path.
315
+ session = client.get_session(options)
316
+ context = Operation::Context.new(
317
+ client: client,
318
+ session: session,
319
+ operation_timeouts: operation_timeouts(options)
320
+ )
321
+ op = Operation::CursorCommand.new(
322
+ selector: command,
323
+ db_name: name,
324
+ read: selector,
325
+ session: session
326
+ )
327
+
328
+ # Per the client-backpressure spec, retrying a generic command on
329
+ # overload errors requires both retryable reads and writes to be
330
+ # enabled, same as Database#command.
331
+ retry_enabled = client.options[:retry_reads] != false &&
332
+ client.options[:retry_writes] != false
333
+
334
+ server = nil
335
+ connection = nil
336
+ cursor = nil
337
+ begin
338
+ result = with_overload_retry(context: context, retry_enabled: retry_enabled) do
339
+ server = selector.select_server(cluster, nil, session)
340
+ if server.load_balancer?
341
+ # The connection is checked in by the cursor when it is drained.
342
+ connection = check_out_cursor_command_connection(server, context)
343
+ begin
344
+ op.execute_with_connection(connection, context: context, options: execution_opts)
345
+ rescue StandardError
346
+ # Release the connection before the error propagates so that
347
+ # a retried attempt checks out a fresh one.
348
+ connection.connection_pool.check_in(connection) unless connection.pinned?
349
+ connection = nil
350
+ raise
351
+ end
352
+ else
353
+ op.execute(server, context: context, options: execution_opts)
354
+ end
355
+ end
356
+
357
+ unless result.cursor?
358
+ raise Error::InvalidCursorOperation,
359
+ 'The command response did not include a cursor. ' \
360
+ 'Use Database#command for commands that do not return a cursor.'
361
+ end
362
+
363
+ view = CursorCommandView.new(self, view_options)
364
+ cursor = Cursor.new(view, result, server, session: session, context: context)
365
+ ensure
366
+ # If the cursor was created it owns the session and connection;
367
+ # otherwise (error or no cursor in the response) release them here.
368
+ unless cursor
369
+ connection.connection_pool.check_in(connection) if connection && !connection.pinned?
370
+ session.end_session if session && session.implicit?
371
+ end
372
+ end
373
+ cursor
374
+ end
375
+
262
376
  # Execute a read command on the database, retrying the read if necessary.
263
377
  #
264
378
  # @param [ Hash ] operation The command to execute.
@@ -530,24 +644,6 @@ module Mongo
530
644
  )
531
645
  end
532
646
 
533
- # Create a database for the provided client, for use when we don't want the
534
- # client's original database instance to be the same.
535
- #
536
- # @api private
537
- #
538
- # @example Create a database for the client.
539
- # Database.create(client)
540
- #
541
- # @param [ Client ] client The client to create on.
542
- #
543
- # @return [ Database ] The database.
544
- #
545
- # @since 2.0.0
546
- def self.create(client)
547
- database = Database.new(client, client.options[:database], client.options)
548
- client.instance_variable_set(:@database, database)
549
- end
550
-
551
647
  # @return [ Integer | nil ] Operation timeout that is for this database or
552
648
  # for the corresponding client.
553
649
  #
@@ -570,5 +666,35 @@ module Mongo
570
666
  end
571
667
  end
572
668
  end
669
+
670
+ private
671
+
672
+ # Removes the getMore and cursor options from the options hash and returns
673
+ # them as a separate hash for the CursorCommandView. The remaining options
674
+ # (e.g. :session, :read, :timeout_ms) are left for command execution.
675
+ #
676
+ # @param [ Hash ] options The cursor_command options (mutated).
677
+ #
678
+ # @return [ Hash ] The view options.
679
+ def extract_cursor_command_view_options(options)
680
+ %i[ batch_size max_time_ms comment cursor_type timeout_mode ].each_with_object({}) do |key, view_options|
681
+ view_options[key] = options.delete(key) if options.key?(key)
682
+ end
683
+ end
684
+
685
+ # Checks out a load balanced connection for a cursor command. If the
686
+ # session is pinned to a connection (e.g. in a transaction), that
687
+ # connection is reused.
688
+ #
689
+ # @param [ Server ] server The load balancer server.
690
+ # @param [ Operation::Context ] context The operation context.
691
+ #
692
+ # @return [ Server::Connection ] The checked out connection.
693
+ def check_out_cursor_command_connection(server, context)
694
+ connection = if context.connection_global_id
695
+ server.pool.check_out_pinned_connection(context.connection_global_id)
696
+ end
697
+ connection || server.pool.check_out(context: context)
698
+ end
573
699
  end
574
700
  end
@@ -23,12 +23,16 @@ module Mongo
23
23
  class InvalidURI < Error
24
24
  # Instantiate the new exception.
25
25
  #
26
+ # The URI is redacted via {Mongo::URI.redact} before being interpolated
27
+ # so that any cleartext credentials in the original input do not end up
28
+ # in logs, error reporters, or backtraces.
29
+ #
26
30
  # @example Instantiate the exception.
27
31
  # Mongo::Error::InvalidURI.new(uri, details, format)
28
32
  #
29
33
  # @since 2.0.0
30
34
  def initialize(uri, details, format = nil)
31
- message = "Bad URI: #{uri}\n" +
35
+ message = "Bad URI: #{Mongo::URI.redact(uri)}\n" +
32
36
  "#{details}\n"
33
37
  message += "MongoDB URI must be in the following format: #{format}\n" if format
34
38
  message += "Please see the following URL for more information: #{Mongo::URI::HELP}\n"
@@ -233,9 +233,9 @@ module Mongo
233
233
  def retrieve_details(document)
234
234
  return nil unless document
235
235
 
236
- if wce = document['writeConcernError']
236
+ if (wce = document['writeConcernError'])
237
237
  wce['errInfo']
238
- elsif we = document['writeErrors']&.first
238
+ elsif (we = document['writeErrors']&.first)
239
239
  we['errInfo']
240
240
  end
241
241
  end
@@ -180,13 +180,13 @@ module Mongo
180
180
  end
181
181
 
182
182
  def parse_single(message, key, doc = document)
183
- return unless error = doc[key]
183
+ return unless (error = doc[key])
184
184
 
185
185
  append(message, error)
186
186
  end
187
187
 
188
188
  def parse_multiple(message, key)
189
- return unless errors = document[key]
189
+ return unless (errors = document[key])
190
190
 
191
191
  errors.each do |error|
192
192
  parse_single(message, 'errmsg', error)
@@ -228,7 +228,7 @@ module Mongo
228
228
  return unless @code.nil? && @code_name.nil?
229
229
  # If we have writeErrors, and all of their codes are the same,
230
230
  # use that code. Otherwise don't set the code
231
- return unless write_errors = document['writeErrors']
231
+ return unless (write_errors = document['writeErrors'])
232
232
 
233
233
  codes = write_errors.map { |e| e['code'] }.compact
234
234
  return unless codes.uniq.length == 1
@@ -29,10 +29,9 @@ module Mongo
29
29
  # Require the error to be communicated at the top level of the response
30
30
  # for it to influence SDAM state. See DRIVERS-1376 / RUBY-2516.
31
31
  return false if document && document['ok'] == 1
32
+ return false if node_recovering?
32
33
 
33
- if node_recovering?
34
- false
35
- elsif code
34
+ if code
36
35
  NOT_MASTER_CODES.include?(code)
37
36
  elsif message
38
37
  message.include?('not master')
@@ -480,7 +480,7 @@ module Mongo
480
480
  #
481
481
  # @since 2.1.0
482
482
  def write_concern
483
- @write_concern ||= if wco = @options[:write_concern] || @options[:write]
483
+ @write_concern ||= if (wco = @options[:write_concern] || @options[:write])
484
484
  WriteConcern.get(wco)
485
485
  else
486
486
  database.write_concern
@@ -494,6 +494,32 @@ module Mongo
494
494
  chunks_collection.drop(timeout_ms: context.remaining_timeout_ms)
495
495
  end
496
496
 
497
+ # Ensures the files and chunks collections have their required indexes,
498
+ # creating any that are missing.
499
+ #
500
+ # @param [ CsotTimeoutHolder | nil ] timeout_holder The timeout holder.
501
+ #
502
+ # @api private
503
+ def ensure_indexes!(timeout_holder = nil)
504
+ fc_idx = files_collection.find(
505
+ {},
506
+ limit: 1,
507
+ projection: { _id: 1 },
508
+ timeout_ms: timeout_holder&.remaining_timeout_ms
509
+ ).first
510
+ create_index_if_missing!(files_collection, FSBucket::FILES_INDEX) if fc_idx.nil?
511
+
512
+ cc_idx = chunks_collection.find(
513
+ {},
514
+ limit: 1,
515
+ projection: { _id: 1 },
516
+ timeout_ms: timeout_holder&.remaining_timeout_ms
517
+ ).first
518
+ return unless cc_idx.nil?
519
+
520
+ create_index_if_missing!(chunks_collection, FSBucket::CHUNKS_INDEX, unique: true)
521
+ end
522
+
497
523
  private
498
524
 
499
525
  # @param [ Hash ] opts The options.
@@ -516,26 +542,6 @@ module Mongo
516
542
  "#{prefix}.#{Grid::File::Info::COLLECTION}"
517
543
  end
518
544
 
519
- def ensure_indexes!(timeout_holder = nil)
520
- fc_idx = files_collection.find(
521
- {},
522
- limit: 1,
523
- projection: { _id: 1 },
524
- timeout_ms: timeout_holder&.remaining_timeout_ms
525
- ).first
526
- create_index_if_missing!(files_collection, FSBucket::FILES_INDEX) if fc_idx.nil?
527
-
528
- cc_idx = chunks_collection.find(
529
- {},
530
- limit: 1,
531
- projection: { _id: 1 },
532
- timeout_ms: timeout_holder&.remaining_timeout_ms
533
- ).first
534
- return unless cc_idx.nil?
535
-
536
- create_index_if_missing!(chunks_collection, FSBucket::CHUNKS_INDEX, unique: true)
537
- end
538
-
539
545
  def create_index_if_missing!(collection, index_spec, options = {})
540
546
  indexes_view = collection.indexes
541
547
  begin
@@ -148,7 +148,7 @@ module Mongo
148
148
  #
149
149
  # @since 2.1.0
150
150
  def write_concern
151
- @write_concern ||= if wco = @options[:write_concern] || @options[:write]
151
+ @write_concern ||= if (wco = @options[:write_concern] || @options[:write])
152
152
  WriteConcern.get(wco)
153
153
  else
154
154
  fs.write_concern
@@ -212,7 +212,7 @@ module Mongo
212
212
  end
213
213
 
214
214
  def ensure_indexes!
215
- fs.send(:ensure_indexes!, @timeout_holder)
215
+ fs.ensure_indexes!(@timeout_holder)
216
216
  end
217
217
 
218
218
  def ensure_open!
@@ -168,11 +168,11 @@ module Mongo
168
168
  options = options.dup
169
169
 
170
170
  create_options = {}
171
- if session = @options[:session]
171
+ if (session = @options[:session])
172
172
  create_options[:session] = session
173
173
  end
174
174
  %i[commit_quorum session comment timeout_ms max_time_ms].each do |key|
175
- if value = options.delete(key)
175
+ if (value = options.delete(key))
176
176
  create_options[key] = value
177
177
  end
178
178
  end
@@ -347,10 +347,20 @@ module Mongo
347
347
  end
348
348
  end
349
349
 
350
+ # The query limit for an index view. Always -1; present so a Cursor
351
+ # can treat an index view like a collection view.
352
+ #
353
+ # @api private
354
+ def limit
355
+ -1
356
+ end
357
+
350
358
  private
351
359
 
352
360
  def drop_by_name(name, opts = {})
353
- client.send(:with_session, @options) do |session|
361
+ session_options = @options.dup
362
+ session_options[:session] = opts[:session] if opts[:session]
363
+ client.with_session(session_options) do |session|
354
364
  spec = {
355
365
  db_name: database.name,
356
366
  coll_name: collection.name,
@@ -394,10 +404,6 @@ module Mongo
394
404
  Operation::Indexes.new(indexes_spec(session))
395
405
  end
396
406
 
397
- def limit
398
- -1
399
- end
400
-
401
407
  def normalize_keys(spec)
402
408
  return false if spec.is_a?(String)
403
409
 
@@ -62,6 +62,7 @@ module Mongo
62
62
  # @since 2.9.0
63
63
  # @api private
64
64
  def initialize(address, reason)
65
+ super()
65
66
  @reason = reason
66
67
  @address = address
67
68
  end
@@ -35,6 +35,7 @@ module Mongo
35
35
  # @since 2.9.0
36
36
  # @api private
37
37
  def initialize(address)
38
+ super()
38
39
  @address = address
39
40
  end
40
41
 
@@ -47,6 +47,7 @@ module Mongo
47
47
  # @since 2.9.0
48
48
  # @api private
49
49
  def initialize(address, id, pool)
50
+ super()
50
51
  @address = address
51
52
  @connection_id = id
52
53
  @pool = pool
@@ -48,6 +48,7 @@ module Mongo
48
48
  # @since 2.9.0
49
49
  # @api private
50
50
  def initialize(address, id, pool)
51
+ super()
51
52
  @address = address
52
53
  @connection_id = id
53
54
  @pool = pool
@@ -79,6 +79,7 @@ module Mongo
79
79
  # @since 2.9.0
80
80
  # @api private
81
81
  def initialize(address, id, reason)
82
+ super()
82
83
  @reason = reason
83
84
  @address = address
84
85
  @connection_id = id
@@ -41,6 +41,7 @@ module Mongo
41
41
  # @since 2.9.0
42
42
  # @api private
43
43
  def initialize(address, id)
44
+ super()
44
45
  @address = address
45
46
  @connection_id = id
46
47
  end
@@ -41,6 +41,7 @@ module Mongo
41
41
  # @since 2.9.0
42
42
  # @api private
43
43
  def initialize(address, id)
44
+ super()
44
45
  @address = address
45
46
  @connection_id = id
46
47
  end
@@ -43,6 +43,7 @@ module Mongo
43
43
  #
44
44
  # @api private
45
45
  def initialize(address, service_id: nil, interrupt_in_use_connections: nil)
46
+ super()
46
47
  @address = address
47
48
  @service_id = service_id
48
49
  @options = {}
@@ -42,6 +42,7 @@ module Mongo
42
42
  # @since 2.9.0
43
43
  # @api private
44
44
  def initialize(address, pool)
45
+ super()
45
46
  @address = address
46
47
  @pool = pool
47
48
  end
@@ -48,6 +48,7 @@ module Mongo
48
48
  # @since 2.9.0
49
49
  # @api private
50
50
  def initialize(address, options, pool)
51
+ super()
51
52
  @address = address
52
53
  @options = options.dup.freeze
53
54
  @pool = pool