mongo 2.25.0 → 2.26.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mongo/bulk_write/combineable.rb +0 -7
  3. data/lib/mongo/bulk_write/validatable.rb +0 -4
  4. data/lib/mongo/bulk_write.rb +0 -34
  5. data/lib/mongo/client.rb +35 -5
  6. data/lib/mongo/client_encryption.rb +36 -11
  7. data/lib/mongo/cluster.rb +1 -2
  8. data/lib/mongo/collection/queryable_encryption.rb +7 -6
  9. data/lib/mongo/collection/view/aggregation.rb +9 -9
  10. data/lib/mongo/collection/view/change_stream.rb +1 -5
  11. data/lib/mongo/collection/view/iterable.rb +2 -9
  12. data/lib/mongo/collection/view/map_reduce.rb +5 -4
  13. data/lib/mongo/collection/view/readable.rb +3 -3
  14. data/lib/mongo/collection/view/writable.rb +0 -35
  15. data/lib/mongo/crypt/auto_encrypter.rb +5 -1
  16. data/lib/mongo/crypt/binding.rb +66 -1
  17. data/lib/mongo/crypt/explicit_encrypter.rb +27 -12
  18. data/lib/mongo/crypt/explicit_encryption_context.rb +51 -12
  19. data/lib/mongo/crypt/handle.rb +18 -0
  20. data/lib/mongo/cursor.rb +13 -0
  21. data/lib/mongo/database/view.rb +7 -6
  22. data/lib/mongo/database.rb +7 -10
  23. data/lib/mongo/error/unsupported_option.rb +0 -40
  24. data/lib/mongo/grid/fs_bucket.rb +2 -2
  25. data/lib/mongo/grid/stream/read.rb +2 -2
  26. data/lib/mongo/grid/stream/write.rb +1 -1
  27. data/lib/mongo/index/view.rb +3 -2
  28. data/lib/mongo/monitoring/event/secure.rb +6 -1
  29. data/lib/mongo/operation/map_reduce/result.rb +6 -2
  30. data/lib/mongo/operation/result.rb +9 -0
  31. data/lib/mongo/operation/shared/executable.rb +1 -6
  32. data/lib/mongo/operation/shared/result/aggregatable.rb +3 -3
  33. data/lib/mongo/protocol/message.rb +0 -4
  34. data/lib/mongo/protocol/msg.rb +1 -12
  35. data/lib/mongo/protocol/serializers.rb +1 -9
  36. data/lib/mongo/retryable/backpressure.rb +17 -2
  37. data/lib/mongo/retryable/read_worker.rb +1 -1
  38. data/lib/mongo/retryable/retry_policy.rb +2 -2
  39. data/lib/mongo/retryable/write_worker.rb +2 -2
  40. data/lib/mongo/retryable.rb +1 -1
  41. data/lib/mongo/server/app_metadata.rb +1 -2
  42. data/lib/mongo/server/connection.rb +7 -0
  43. data/lib/mongo/server/connection_pool.rb +60 -9
  44. data/lib/mongo/server/description/features.rb +2 -3
  45. data/lib/mongo/server/description.rb +0 -1
  46. data/lib/mongo/server/pending_connection.rb +0 -5
  47. data/lib/mongo/server.rb +1 -2
  48. data/lib/mongo/session.rb +10 -3
  49. data/lib/mongo/tracing/open_telemetry/command_tracer.rb +141 -102
  50. data/lib/mongo/tracing/open_telemetry/operation_tracer.rb +52 -17
  51. data/lib/mongo/tracing/open_telemetry/tracer.rb +10 -1
  52. data/lib/mongo/uri/options_mapper.rb +15 -4
  53. data/lib/mongo/version.rb +1 -1
  54. metadata +2 -2
@@ -37,12 +37,19 @@ module Mongo
37
37
  # to TLS connection options of Mongo::Client.
38
38
  # @param [ Integer | nil ] timeout_ms Timeout for every operation executed
39
39
  # on this object.
40
- def initialize(key_vault_client, key_vault_namespace, kms_providers, kms_tls_options, timeout_ms = nil)
40
+ # @param [ Integer | nil ] key_expiration_ms The lifetime of the data
41
+ # encryption key cache, in milliseconds. A value of 0 means the cache
42
+ # never expires. When nil, libmongocrypt's default of 60000 is used.
43
+ def initialize(
44
+ key_vault_client, key_vault_namespace, kms_providers, kms_tls_options,
45
+ timeout_ms = nil, key_expiration_ms = nil
46
+ )
41
47
  Crypt.validate_ffi!
42
48
  @crypt_handle = Handle.new(
43
49
  kms_providers,
44
50
  kms_tls_options,
45
- explicit_encryption_only: true
51
+ explicit_encryption_only: true,
52
+ key_expiration_ms: key_expiration_ms
46
53
  )
47
54
  @encryption_io = EncryptionIO.new(
48
55
  key_vault_client: key_vault_client,
@@ -93,23 +100,31 @@ module Mongo
93
100
  # encryption key.
94
101
  # @option options [ String ] :algorithm The algorithm used to encrypt the value.
95
102
  # Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
96
- # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed".
103
+ # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed",
104
+ # "Range", "String".
97
105
  # @option options [ Integer | nil ] :contention_factor Contention factor
98
- # to be applied if encryption algorithm is set to "Indexed". If not
99
- # provided, it defaults to a value of 0. Contention factor should be set
100
- # only if encryption algorithm is set to "Indexed".
101
- # @option options [ String | nil ] query_type Query type to be applied
102
- # if encryption algorithm is set to "Indexed". Query type should be set
103
- # only if encryption algorithm is set to "Indexed". The only allowed
104
- # value is "equality".
106
+ # to be applied if encryption algorithm is set to "Indexed", "Range", or
107
+ # "String". If not provided, it defaults to a value of 0. Contention
108
+ # factor should be set only if encryption algorithm is set to "Indexed",
109
+ # "Range", or "String".
110
+ # @option options [ String | nil ] :query_type Query type to be applied
111
+ # if encryption algorithm is set to "Indexed", "Range", or "String".
112
+ # Allowed values are "equality" (for "Indexed"), "range" (for "Range"),
113
+ # and "prefix", "suffix", "substring" (for "String").
114
+ # @option options [ Hash | nil ] :range_opts Specifies index options for a
115
+ # Queryable Encryption field supporting "range" queries. Required when
116
+ # algorithm is "Range".
117
+ # @option options [ Hash | nil ] :string_opts Specifies index options for a
118
+ # Queryable Encryption field supporting "prefix", "suffix", or
119
+ # "substring" queries. Required when algorithm is "String".
105
120
  #
106
121
  # @note The :key_id and :key_alt_name options are mutually exclusive. Only
107
122
  # one is required to perform explicit encryption.
108
123
  #
109
124
  # @return [ BSON::Binary ] A BSON Binary object of subtype 6 (ciphertext)
110
125
  # representing the encrypted value
111
- # @raise [ ArgumentError ] if either contention_factor or query_type
112
- # is set, and algorithm is not "Indexed".
126
+ # @raise [ ArgumentError ] if either contention_factor or query_type is
127
+ # set, and algorithm is not "Indexed", "Range", or "String".
113
128
  def encrypt(value, options)
114
129
  Crypt::ExplicitEncryptionContext.new(
115
130
  @crypt_handle,
@@ -36,14 +36,16 @@ module Mongo
36
36
  # that will be used to encrypt the value.
37
37
  # @option options [ String ] :algorithm The algorithm used to encrypt the
38
38
  # value. Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
39
- # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "Range".
39
+ # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "Range",
40
+ # "String".
40
41
  # @option options [ Integer | nil ] :contention_factor Contention factor
41
- # to be applied if encryption algorithm is set to "Indexed". If not
42
- # provided, it defaults to a value of 0. Contention factor should be set
43
- # only if encryption algorithm is set to "Indexed".
42
+ # to be applied if encryption algorithm is set to "Indexed", "Range", or "String".
43
+ # If not provided, it defaults to a value of 0. Contention factor should be set
44
+ # only if encryption algorithm is set to "Indexed", "Range", or "String".
44
45
  # @option options [ String | nil ] query_type Query type to be applied
45
- # if encryption algorithm is set to "Indexed" or "Range".
46
- # Allowed values are "equality" and "range".
46
+ # if encryption algorithm is set to "Indexed", "Range", or "String".
47
+ # Allowed values are "equality", "range", "prefix", "suffix", and
48
+ # "substring".
47
49
  # @option options [ Hash | nil ] :range_opts Specifies index options for
48
50
  # a Queryable Encryption field supporting "range" queries.
49
51
  # Allowed options are:
@@ -56,9 +58,22 @@ module Mongo
56
58
  # the encryptedFields of the destination collection.
57
59
  # For double and decimal128, min/max/precision must all be set,
58
60
  # or all be unset.
61
+ # @option options [ Hash | nil ] :string_opts Specifies index options for
62
+ # a Queryable Encryption field supporting "prefix", "suffix", or
63
+ # "substring" queries (algorithm "String"). Allowed options are:
64
+ # - :case_sensitive
65
+ # - :diacritic_sensitive
66
+ # - :prefix (Hash with :str_min_query_length, :str_max_query_length)
67
+ # - :suffix (Hash with :str_min_query_length, :str_max_query_length)
68
+ # - :substring (Hash with :str_max_length, :str_min_query_length,
69
+ # :str_max_query_length)
70
+ # The options must match the values set in the encryptedFields of the
71
+ # destination collection.
59
72
  #
60
73
  # @note The Range algorithm is experimental only. It is not intended for
61
74
  # public use.
75
+ # @note The "substring" query type is unstable and subject to backwards
76
+ # breaking changes.
62
77
  #
63
78
  # @raise [ ArgumentError|Mongo::Error::CryptError ] If invalid options are provided
64
79
  def initialize(mongocrypt, io, doc, options = {})
@@ -114,28 +129,52 @@ module Mongo
114
129
 
115
130
  def set_algorithm_opts(options)
116
131
  Binding.ctx_setopt_algorithm(self, options[:algorithm])
117
- if %w[Indexed Range].include?(options[:algorithm])
132
+ if %w[Indexed Range String].include?(options[:algorithm])
118
133
  Binding.ctx_setopt_contention_factor(self, options[:contention_factor]) if options[:contention_factor]
119
134
  Binding.ctx_setopt_query_type(self, options[:query_type]) if options[:query_type]
120
135
  else
121
136
  if options[:contention_factor]
122
- raise ArgumentError.new(':contention_factor is allowed only for "Indexed" or "Range" algorithms')
137
+ raise ArgumentError.new(':contention_factor is allowed only for "Indexed", "Range", or "String" algorithms')
123
138
  end
124
139
  if options[:query_type]
125
- raise ArgumentError.new(':query_type is allowed only for "Indexed" or "Range" algorithms')
140
+ raise ArgumentError.new(':query_type is allowed only for "Indexed", "Range", or "String" algorithms')
126
141
  end
127
142
  end
128
- return unless options[:algorithm] == 'Range'
129
-
130
- Binding.ctx_setopt_algorithm_range(self, convert_range_opts(options[:range_opts]))
143
+ if options[:algorithm] == 'Range'
144
+ Binding.ctx_setopt_algorithm_range(self, convert_range_opts(options[:range_opts]))
145
+ elsif options[:algorithm] == 'String'
146
+ Binding.ctx_setopt_algorithm_text(self, convert_string_opts(options[:string_opts]))
147
+ end
131
148
  end
132
149
 
133
150
  def convert_range_opts(range_opts)
151
+ raise ArgumentError.new(':range_opts is required for the "Range" algorithm') if range_opts.nil?
152
+
134
153
  range_opts.dup.tap do |opts|
135
154
  opts[:sparsity] = BSON::Int64.new(opts[:sparsity]) if opts[:sparsity] && !opts[:sparsity].is_a?(BSON::Int64)
136
155
  opts[:trimFactor] = opts.delete(:trim_factor) if opts[:trim_factor]
137
156
  end
138
157
  end
158
+
159
+ def convert_string_opts(string_opts)
160
+ raise ArgumentError.new(':string_opts is required for the "String" algorithm') if string_opts.nil?
161
+
162
+ string_opts.dup.tap do |opts|
163
+ opts[:caseSensitive] = opts.delete(:case_sensitive) if opts.key?(:case_sensitive)
164
+ opts[:diacriticSensitive] = opts.delete(:diacritic_sensitive) if opts.key?(:diacritic_sensitive)
165
+ %i[substring prefix suffix].each do |query_type|
166
+ opts[query_type] = convert_string_query_opts(opts[query_type]) if opts[query_type]
167
+ end
168
+ end
169
+ end
170
+
171
+ def convert_string_query_opts(query_opts)
172
+ query_opts.dup.tap do |opts|
173
+ opts[:strMaxLength] = opts.delete(:str_max_length) if opts.key?(:str_max_length)
174
+ opts[:strMinQueryLength] = opts.delete(:str_min_query_length) if opts.key?(:str_min_query_length)
175
+ opts[:strMaxQueryLength] = opts.delete(:str_max_query_length) if opts.key?(:str_max_query_length)
176
+ end
177
+ end
139
178
  end
140
179
  end
141
180
  end
@@ -66,6 +66,10 @@ module Mongo
66
66
  # error that libmongocrypt raises on a subsequent "$SYSTEM" search.
67
67
  # @option options [ Logger ] :logger A Logger object to which libmongocrypt logs
68
68
  # will be sent
69
+ # @option options [ Integer | nil ] :key_expiration_ms The lifetime of the
70
+ # data encryption key cache, in milliseconds. A value of 0 means the
71
+ # cache never expires. When nil, libmongocrypt's default of 60000 is
72
+ # used.
69
73
  def initialize(kms_providers, kms_tls_options, options = {})
70
74
  # FFI::AutoPointer uses a custom release strategy to automatically free
71
75
  # the pointer once this object goes out of scope
@@ -87,6 +91,9 @@ module Mongo
87
91
  @bypass_query_analysis = options[:bypass_query_analysis]
88
92
  set_bypass_query_analysis if @bypass_query_analysis
89
93
 
94
+ @key_expiration_ms = options[:key_expiration_ms]
95
+ set_key_expiration unless @key_expiration_ms.nil?
96
+
90
97
  @crypt_shared_lib_path = options[:crypt_shared_lib_path]
91
98
  @explicit_encryption_only = options[:explicit_encryption_only]
92
99
  @disable_crypt_shared_lib_search = options[:disable_crypt_shared_lib_search]
@@ -200,6 +207,17 @@ module Mongo
200
207
  Binding.setopt_bypass_query_analysis(self) if @bypass_query_analysis
201
208
  end
202
209
 
210
+ def set_key_expiration
211
+ unless @key_expiration_ms.is_a?(Integer) && !@key_expiration_ms.negative?
212
+ raise ArgumentError.new(
213
+ "#{@key_expiration_ms} is an invalid key_expiration_ms value; " \
214
+ 'must be a non-negative Integer or nil'
215
+ )
216
+ end
217
+
218
+ Binding.setopt_key_expiration(self, @key_expiration_ms)
219
+ end
220
+
203
221
  # Send the logs from libmongocrypt to the Mongo::Logger
204
222
  def set_logger_callback
205
223
  @log_callback = proc do |level, msg|
data/lib/mongo/cursor.rb CHANGED
@@ -103,6 +103,19 @@ module Mongo
103
103
  self.class.finalize(kill_spec(@connection_global_id), cluster)
104
104
  )
105
105
  end
106
+ rescue Exception # rubocop:disable Lint/RescueException
107
+ # In load-balanced topology the connection of the initial result is
108
+ # checked out of the pool until the cursor is drained. If the cursor
109
+ # cannot be constructed, nothing will ever check the connection back
110
+ # in, so release it here before the error propagates. Exception (not
111
+ # StandardError) is rescued so that an interrupt does not permanently
112
+ # leak the connection. In other topologies the connection is not owned
113
+ # by the cursor and must not be touched here.
114
+ if server&.load_balancer? && result.is_a?(Operation::Result) &&
115
+ (connection = result.connection)
116
+ connection.connection_pool.check_in_if_checked_out(connection)
117
+ end
118
+ raise
106
119
  end
107
120
 
108
121
  # @api private
@@ -273,12 +273,13 @@ module Mongo
273
273
  execution_opts = {}
274
274
  execution_opts[:deserialize_as_bson] = opts.delete(:deserialize_as_bson) if opts.key?(:deserialize_as_bson)
275
275
  if server.load_balancer?
276
- connection = server.pool.check_out(context: context)
277
- initial_query_op(session, opts).execute_with_connection(
278
- connection,
279
- context: context,
280
- options: execution_opts
281
- )
276
+ server.pool.with_cursor_connection(context: context) do |connection|
277
+ initial_query_op(session, opts).execute_with_connection(
278
+ connection,
279
+ context: context,
280
+ options: execution_opts
281
+ )
282
+ end
282
283
  else
283
284
  initial_query_op(session, opts).execute(
284
285
  server,
@@ -339,15 +339,12 @@ module Mongo
339
339
  server = selector.select_server(cluster, nil, session)
340
340
  if server.load_balancer?
341
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
342
+ # If the command fails, the connection is released before the
343
+ # error propagates so that a retried attempt checks out a
344
+ # fresh one.
345
+ server.pool.with_cursor_connection(context: context) do |conn|
346
+ connection = conn
347
+ op.execute_with_connection(conn, context: context, options: execution_opts)
351
348
  end
352
349
  else
353
350
  op.execute(server, context: context, options: execution_opts)
@@ -366,7 +363,7 @@ module Mongo
366
363
  # If the cursor was created it owns the session and connection;
367
364
  # otherwise (error or no cursor in the response) release them here.
368
365
  unless cursor
369
- connection.connection_pool.check_in(connection) if connection && !connection.pinned?
366
+ connection.connection_pool.check_in_if_checked_out(connection) if connection
370
367
  session.end_session if session && session.implicit?
371
368
  end
372
369
  end
@@ -18,24 +18,6 @@ module Mongo
18
18
  class Error
19
19
  # Raised if an unsupported option is specified for an operation.
20
20
  class UnsupportedOption < Error
21
- # The error message provided when the user passes the hint option to
22
- # a write operation against a server that does not support the hint
23
- # option and does not provide option validation.
24
- #
25
- # @api private
26
- HINT_MESSAGE = 'The MongoDB server handling this request does not support ' \
27
- 'the hint option on this command. The hint option is supported on update ' \
28
- 'commands on MongoDB server versions 4.2 and later and on findAndModify ' \
29
- 'and delete commands on MongoDB server versions 4.4 and later'
30
-
31
- # The error message provided when the user passes the hint option to
32
- # an unacknowledged write operation.
33
- #
34
- # @api private
35
- UNACKNOWLEDGED_HINT_MESSAGE = 'The hint option cannot be specified on ' \
36
- 'an unacknowledged write operation. Remove the hint option or perform ' \
37
- 'this operation with a write concern of at least { w: 1 }'
38
-
39
21
  # The error message provided when the user passes the allow_disk_use
40
22
  # option to a find operation against a server that does not support the
41
23
  # allow_disk_use operation and does not provide option validation.
@@ -56,28 +38,6 @@ module Mongo
56
38
  'option is supported on createIndexes commands on MongoDB server versions ' \
57
39
  '4.4 and later'
58
40
 
59
- # Raise an error about an unsupported hint option.
60
- #
61
- # @option options [ Boolean ] unacknowledged_write Whether this error
62
- # pertains to a hint option passed to an unacknowledged write. Defaults
63
- # to false.
64
- #
65
- # @return [ Mongo::Error::UnsupportedOption ] An error with a default
66
- # error message.
67
- #
68
- # @api private
69
- def self.hint_error(**options)
70
- unacknowledged_write = options[:unacknowledged_write] || false
71
-
72
- error_message = if unacknowledged_write
73
- UNACKNOWLEDGED_HINT_MESSAGE
74
- else
75
- HINT_MESSAGE
76
- end
77
-
78
- new(error_message)
79
- end
80
-
81
41
  # Raise an error about an unsupported allow_disk_use option.
82
42
  #
83
43
  # @return [ Mongo::Error::UnsupportedOption ] An error with a default
@@ -218,10 +218,10 @@ module Mongo
218
218
  def delete(id, opts = {})
219
219
  timeout_holder = CsotTimeoutHolder.new(operation_timeouts: operation_timeouts(opts))
220
220
  result = files_collection
221
- .find({ _id: id }, @options.merge(timeout_ms: timeout_holder.remaining_timeout_ms))
221
+ .find({ _id: { '$eq' => id } }, @options.merge(timeout_ms: timeout_holder.remaining_timeout_ms))
222
222
  .delete_one(timeout_ms: timeout_holder.remaining_timeout_ms)
223
223
  chunks_collection
224
- .find({ files_id: id }, @options.merge(timeout_ms: timeout_holder.remaining_timeout_ms))
224
+ .find({ files_id: { '$eq' => id } }, @options.merge(timeout_ms: timeout_holder.remaining_timeout_ms))
225
225
  .delete_many(timeout_ms: timeout_holder.remaining_timeout_ms)
226
226
  raise Error::FileNotFound.new(id, :id) if result.n == 0
227
227
 
@@ -182,7 +182,7 @@ module Mongo
182
182
  @file_info ||= begin
183
183
  doc = options[:file_info_doc] ||
184
184
  fs.files_collection.find(
185
- { _id: file_id },
185
+ { _id: { '$eq' => file_id } },
186
186
  { timeout_ms: @timeout_holder.remaining_timeout_ms! }
187
187
  ).first
188
188
  File::Info.new(Options::Mapper.transform(doc, File::Info::MAPPINGS.invert)) if doc
@@ -216,7 +216,7 @@ module Mongo
216
216
  opts[:timeout_mode] = :cursor_lifetime
217
217
  end
218
218
 
219
- fs.chunks_collection.find({ files_id: file_id }, opts).sort(n: 1)
219
+ fs.chunks_collection.find({ files_id: { '$eq' => file_id } }, opts).sort(n: 1)
220
220
  end
221
221
  end
222
222
 
@@ -177,7 +177,7 @@ module Mongo
177
177
  # @since 2.1.0
178
178
  def abort
179
179
  fs.chunks_collection.find(
180
- { files_id: file_id },
180
+ { files_id: { '$eq' => file_id } },
181
181
  @options.merge(timeout_ms: @timeout_holder.remaining_timeout_ms!)
182
182
  ).delete_many
183
183
  (@open = false) || true
@@ -421,8 +421,9 @@ module Mongo
421
421
 
422
422
  def send_initial_query(op, server, _session, context)
423
423
  if server.load_balancer?
424
- connection = server.pool.check_out(context: context)
425
- op.execute_with_connection(connection, context: context)
424
+ server.pool.with_cursor_connection(context: context) do |connection|
425
+ op.execute_with_connection(connection, context: context)
426
+ end
426
427
  else
427
428
  op.execute(server, context: context)
428
429
  end
@@ -37,6 +37,11 @@ module Mongo
37
37
  copydb
38
38
  ].freeze
39
39
 
40
+ # The hello and legacy hello command names. A constant, because
41
+ # sensitive? runs on every command event and an array literal would
42
+ # allocate on each call.
43
+ HELLO_COMMANDS = %w[hello ismaster isMaster].freeze
44
+
40
45
  # Check whether the command is sensitive in terms of command monitoring
41
46
  # spec. A command is detected as sensitive if it is in the
42
47
  # list or if it is a hello/legacy hello command, and
@@ -53,7 +58,7 @@ module Mongo
53
58
  # when speculativeAuthenticate is present, their commands AND replies
54
59
  # MUST be redacted from the events.
55
60
  # See https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.md#security
56
- %w[hello ismaster isMaster].include?(command_name.to_s) &&
61
+ HELLO_COMMANDS.include?(command_name.to_s) &&
57
62
  !!document['speculativeAuthenticate']
58
63
  end
59
64
 
@@ -80,7 +80,7 @@ module Mongo
80
80
  # @since 2.0.0
81
81
  # @api public
82
82
  def successful?
83
- !documents.nil?
83
+ !documents.nil? && parser.message.empty?
84
84
  end
85
85
 
86
86
  # Get the execution time of the map/reduce.
@@ -112,7 +112,11 @@ module Mongo
112
112
  # @since 2.0.0
113
113
  # @api private
114
114
  def validate!
115
- documents.nil? ? raise_operation_failure : self
115
+ if documents.nil? || !parser.message.empty?
116
+ raise_operation_failure
117
+ else
118
+ self
119
+ end
116
120
  end
117
121
 
118
122
  # Get the cursor id.
@@ -456,6 +456,15 @@ module Mongo
456
456
  doc['cursor']&.[]('atClusterTime') || doc['atClusterTime']
457
457
  end
458
458
 
459
+ # Returns the base backoff in milliseconds for a server overload error, if present.
460
+ #
461
+ # @return [ Integer | nil ] The base backoff in milliseconds.
462
+ #
463
+ # @api private
464
+ def base_backoff_ms
465
+ first_document && first_document['baseBackoffMS']
466
+ end
467
+
459
468
  private
460
469
 
461
470
  def operation_failure_class
@@ -132,12 +132,7 @@ module Mongo
132
132
  def process_result_for_sdam(result, connection)
133
133
  if (result.not_master? || result.node_recovering?) &&
134
134
  connection.generation >= connection.server.pool.generation(service_id: connection.service_id)
135
- keep_pool = if result.node_shutting_down?
136
- false
137
- else
138
- # Max wire version needs to be examined while the server is known
139
- connection.description.server_version_gte?('4.2')
140
- end
135
+ keep_pool = !result.node_shutting_down?
141
136
 
142
137
  connection.server.unknown!(
143
138
  keep_connection_pool: keep_pool,
@@ -36,7 +36,7 @@ module Mongo
36
36
  return unless @replies
37
37
 
38
38
  @replies.reduce(nil) do |errors, reply|
39
- next unless (write_errors = reply.documents.first['writeErrors'])
39
+ next errors unless (write_errors = reply.documents.first['writeErrors'])
40
40
 
41
41
  wes = write_errors.collect do |we|
42
42
  we.merge!('index' => count + we['index'])
@@ -59,11 +59,11 @@ module Mongo
59
59
  return unless @replies
60
60
 
61
61
  @replies.each_with_index.reduce(nil) do |errors, (reply, _)|
62
- next unless (write_concern_errors = reply.documents.first['writeConcernErrors'])
62
+ next errors unless (write_concern_errors = reply.documents.first['writeConcernErrors'])
63
63
 
64
64
  (errors || []) << write_concern_errors.reduce(nil) do |errs, wce|
65
65
  wce.merge!('index' => count + wce['index'])
66
- (errs || []) << write_concern_error
66
+ (errs || []) << wce
67
67
  end
68
68
  end
69
69
  end
@@ -148,10 +148,6 @@ module Mongo
148
148
  # @return [ Mongo::Protocol::Msg ] The decrypted message, or the original
149
149
  # message if decryption was not possible or necessary.
150
150
  def maybe_decrypt(_context)
151
- # TODO: determine if we should be decrypting data coming from pre-4.2
152
- # servers, potentially using legacy wire protocols. If so we need
153
- # to implement decryption for those wire protocols as our current
154
- # encryption/decryption code is OP_MSG-specific.
155
151
  self
156
152
  end
157
153
 
@@ -201,23 +201,12 @@ module Mongo
201
201
  # represents one of the command types allow-listed by libmongocrypt and it
202
202
  # contains data that is required to be encrypted by a local or remote json schema.
203
203
  #
204
- # @param [ Mongo::Server::Connection ] connection The connection on which
205
- # the operation is performed.
206
204
  # @param [ Mongo::Operation::Context ] context The operation context.
207
205
  #
208
206
  # @return [ Mongo::Protocol::Msg ] The encrypted message, or the original
209
207
  # message if encryption was not possible or necessary.
210
- def maybe_encrypt(connection, context)
208
+ def maybe_encrypt(_connection, context)
211
209
  if context.encrypt?
212
- if connection.description.max_wire_version < 8
213
- raise Error::CryptError.new(
214
- 'Cannot perform encryption against a MongoDB server older than ' +
215
- '4.2 (wire version less than 8). Currently connected to server ' +
216
- "with max wire version #{connection.description.max_wire_version}} " +
217
- '(Auto-encryption requires a minimum MongoDB version of 4.2)'
218
- )
219
- end
220
-
221
210
  db_name = @main_document[DATABASE_IDENTIFIER]
222
211
  cmd = merge_sections
223
212
  enc_cmd = context.encrypt(db_name, cmd)
@@ -319,16 +319,8 @@ module Mongo
319
319
  # @return [ Array<BSON::Document> ] Deserialized section.
320
320
  #
321
321
  # @since 2.5.0
322
- def self.deserialize(buffer)
322
+ def self.deserialize(buffer, _options = {})
323
323
  raise NotImplementedError
324
-
325
- start_size = buffer.length
326
- section_size = buffer.get_int32 # get the size
327
- end_size = start_size - section_size
328
- buffer.get_cstring # get the identifier
329
- documents = []
330
- documents << BSON::Document.from_bson(buffer) until buffer.length == end_size
331
- documents
332
324
  end
333
325
  end
334
326
  end
@@ -23,9 +23,24 @@ module Mongo
23
23
  # a random value. Can be injected for deterministic testing.
24
24
  #
25
25
  # @return [ Float ] The backoff delay in seconds.
26
- def self.backoff_delay(attempt, jitter: rand)
27
- jitter * [ MAX_BACKOFF, BASE_BACKOFF * (2**(attempt - 1)) ].min
26
+ def self.backoff_delay(attempt, jitter: rand, err: nil)
27
+ jitter * [ MAX_BACKOFF, base_backoff(err) * (2**attempt) ].min
28
28
  end
29
+
30
+ def self.base_backoff(err)
31
+ return BASE_BACKOFF if err.nil?
32
+ return BASE_BACKOFF unless err.respond_to?(:result) && err.result.respond_to?(:base_backoff_ms)
33
+
34
+ base_backoff_ms = err.result.base_backoff_ms
35
+
36
+ if base_backoff_ms && base_backoff_ms > 0
37
+ err.result.base_backoff_ms / 1000.0
38
+ else
39
+ BASE_BACKOFF
40
+ end
41
+ end
42
+
43
+ private_class_method :base_backoff
29
44
  end
30
45
  end
31
46
  end
@@ -340,7 +340,7 @@ module Mongo
340
340
  def overload_read_retry(last_error, session, server_selector, context, failed_server, error_count:)
341
341
  last_was_overload = true
342
342
  loop do
343
- delay = last_was_overload ? retry_policy.backoff_delay(error_count) : 0
343
+ delay = last_was_overload ? retry_policy.backoff_delay(error_count, err: last_error) : 0
344
344
  raise last_error unless retry_policy.should_retry_overload?(error_count, delay, context: context)
345
345
 
346
346
  log_retry(last_error, message: 'Read retry (overload backoff)')
@@ -27,8 +27,8 @@ module Mongo
27
27
  # @param [ Float ] jitter A random float in [0.0, 1.0).
28
28
  #
29
29
  # @return [ Float ] The backoff delay in seconds.
30
- def backoff_delay(attempt, jitter: rand)
31
- Backpressure.backoff_delay(attempt, jitter: jitter)
30
+ def backoff_delay(attempt, jitter: rand, err: nil)
31
+ Backpressure.backoff_delay(attempt, jitter: jitter, err: err)
32
32
  end
33
33
 
34
34
  # Determine whether an overload retry should be attempted.
@@ -123,7 +123,7 @@ module Mongo
123
123
  unless e.respond_to?(:label?) && e.label?('NoWritesPerformed')
124
124
  error_to_raise = e
125
125
  end
126
- delay = retry_policy.backoff_delay(error_count)
126
+ delay = retry_policy.backoff_delay(error_count, err: e)
127
127
  raise error_to_raise unless retry_policy.should_retry_overload?(error_count, delay, context: context)
128
128
 
129
129
  log_retry(e, message: 'Write retry (overload backoff)')
@@ -386,7 +386,7 @@ module Mongo
386
386
  last_was_overload = true
387
387
 
388
388
  loop do
389
- delay = last_was_overload ? retry_policy.backoff_delay(error_count) : 0
389
+ delay = last_was_overload ? retry_policy.backoff_delay(error_count, err: last_error) : 0
390
390
  raise error_to_raise unless retry_policy.should_retry_overload?(error_count, delay, context: context)
391
391
 
392
392
  log_retry(last_error, message: 'Write retry (overload backoff)')
@@ -129,7 +129,7 @@ module Mongo
129
129
 
130
130
  error_count += 1
131
131
  policy = client.retry_policy
132
- delay = policy.backoff_delay(error_count)
132
+ delay = policy.backoff_delay(error_count, err: e)
133
133
  raise e unless policy.should_retry_overload?(error_count, delay, context: context)
134
134
 
135
135
  Logger.logger.warn("Overload retry due to: #{e.class.name}: #{e.message}")
@@ -130,7 +130,6 @@ module Mongo
130
130
  doc[:driver] = driver_doc
131
131
  doc[:os] = os_doc
132
132
  doc[:platform] = platform_string
133
- doc[:backpressure] = true
134
133
  env_doc.tap { |env| doc[:env] = env if env }
135
134
  end
136
135
  end
@@ -157,7 +156,7 @@ module Mongo
157
156
  def document
158
157
  @document ||= begin
159
158
  client = Truncator.new(client_document).document
160
- BSON::Document.new(compression: @compressors, client: client).tap do |doc|
159
+ BSON::Document.new(compression: @compressors, client: client, backpressure: '2').tap do |doc|
161
160
  doc[:saslSupportedMechs] = @request_auth_mech if @request_auth_mech
162
161
  doc.update(Utils.transform_server_api(@server_api)) if @server_api
163
162
  end