mongo 2.25.0 → 2.26.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 (36) 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/writable.rb +0 -35
  10. data/lib/mongo/crypt/auto_encrypter.rb +5 -1
  11. data/lib/mongo/crypt/binding.rb +66 -1
  12. data/lib/mongo/crypt/explicit_encrypter.rb +27 -12
  13. data/lib/mongo/crypt/explicit_encryption_context.rb +51 -12
  14. data/lib/mongo/crypt/handle.rb +18 -0
  15. data/lib/mongo/error/unsupported_option.rb +0 -40
  16. data/lib/mongo/grid/fs_bucket.rb +2 -2
  17. data/lib/mongo/grid/stream/read.rb +2 -2
  18. data/lib/mongo/grid/stream/write.rb +1 -1
  19. data/lib/mongo/operation/result.rb +9 -0
  20. data/lib/mongo/operation/shared/executable.rb +1 -6
  21. data/lib/mongo/protocol/message.rb +0 -4
  22. data/lib/mongo/protocol/msg.rb +1 -12
  23. data/lib/mongo/retryable/backpressure.rb +17 -2
  24. data/lib/mongo/retryable/read_worker.rb +1 -1
  25. data/lib/mongo/retryable/retry_policy.rb +2 -2
  26. data/lib/mongo/retryable/write_worker.rb +2 -2
  27. data/lib/mongo/retryable.rb +1 -1
  28. data/lib/mongo/server/app_metadata.rb +1 -1
  29. data/lib/mongo/server/description/features.rb +2 -3
  30. data/lib/mongo/server/description.rb +0 -1
  31. data/lib/mongo/server/pending_connection.rb +0 -5
  32. data/lib/mongo/server.rb +1 -2
  33. data/lib/mongo/session.rb +3 -3
  34. data/lib/mongo/uri/options_mapper.rb +15 -4
  35. data/lib/mongo/version.rb +1 -1
  36. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 846c673e8e85d3e7007bec794833276ed4da9591347e361465b190c10829c504
4
- data.tar.gz: ad93e6b640fbdb3c6f757df0c0ae433f4c79d50c209fb0bd3c0b8c84402148a9
3
+ metadata.gz: 439e028e8b9ce062d109e1760508977f13a87dd6ee4cb9d5221a6a3108fec214
4
+ data.tar.gz: bb346a893717256c3eac12af4a63c718bfd4f567b1ec17b95727e45f9f8e11d5
5
5
  SHA512:
6
- metadata.gz: 29750ebefccf852b621847e68bffd039f3e28c63829334880416d7e8348fbd8cc5769d7f5b1cf872030d874610739ef4df90f493d9b325d8bebcfe2dd7d7b638
7
- data.tar.gz: fbb6248041e00a490ccf23e62c364ce3c8ea8214f3fd4c6f07ff8df18e090702c53039e30a1b2b66f8a5e464ebf4e9162d993a0c67cd0818911449a91d168b3f
6
+ metadata.gz: 3d650edf8067fbe760d1c0526e62bef7cfda7c27284b725ffcc5a8e3c33c712011b3aa478e87db2abea876d36e332daf733d1180954ed6bd1e5003a258a05f49
7
+ data.tar.gz: 6832d11c4cae68a7776c8e5f7ef8e5bd9fcbda45bedf8e8cb208fdc5103060582a315cb75434e003462500b4da05deb5e71618a6856159262ff3655fbe16888d
@@ -39,7 +39,6 @@ module Mongo
39
39
  @requests = requests
40
40
  @has_collation = false
41
41
  @has_array_filters = false
42
- @has_hint = false
43
42
  end
44
43
 
45
44
  # @return [ Boolean ] Whether one or more operation specifies the collation
@@ -54,12 +53,6 @@ module Mongo
54
53
  @has_array_filters
55
54
  end
56
55
 
57
- # @return [ Boolean ] Whether one or more operation specifies the
58
- # hint option.
59
- def has_hint?
60
- @has_hint
61
- end
62
-
63
56
  private
64
57
 
65
58
  def combine_requests(ops)
@@ -43,10 +43,6 @@ module Mongo
43
43
  @has_collation = true if document.respond_to?(:keys) && (document[:collation] || document[Operation::COLLATION])
44
44
 
45
45
  @has_array_filters = true if document.respond_to?(:keys) && document[:array_filters]
46
-
47
- return unless document.respond_to?(:keys) && document[:hint]
48
-
49
- @has_hint = true
50
46
  end
51
47
 
52
48
  private
@@ -236,8 +236,6 @@ module Mongo
236
236
  end
237
237
 
238
238
  def execute_operation(name, values, connection, context, operation_id, result_combiner, session, txn_num = nil)
239
- validate_hint!(connection)
240
-
241
239
  unpin_maybe(session, connection) do
242
240
  if values.size > connection.description.max_write_batch_size
243
241
  split_execute(name, values, connection, context, operation_id, result_combiner, session, txn_num)
@@ -310,38 +308,6 @@ module Mongo
310
308
  Operation::Update.new(spec).bulk_execute(connection, context: context)
311
309
  end
312
310
 
313
- def validate_hint!(connection)
314
- return unless op_combiner.has_hint?
315
- return unless !can_hint?(connection) && write_concern && !write_concern.acknowledged?
316
-
317
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
318
- end
319
-
320
- # Loop through the requests and check if each operation is allowed to send
321
- # a hint for each operation on the given server version.
322
- #
323
- # For the following operations, the client can send a hint for all supported
324
- # server versions, and for the rest, the client can only send it for 4.4+:
325
- # - updateOne
326
- # - updateMany
327
- # - replaceOne
328
- #
329
- # @param [ Connection ] connection The connection object.
330
- #
331
- # @return [ true | false ] Whether the request is able to send hints for
332
- # the current server version.
333
- def can_hint?(connection)
334
- gte_4_4 = connection.server.description.server_version_gte?('4.4')
335
- op_combiner.requests.all? do |req|
336
- op = req.keys.first
337
- if req[op].keys.include?(:hint)
338
- %i[update_one update_many replace_one].include?(op) || gte_4_4
339
- else
340
- true
341
- end
342
- end
343
- end
344
-
345
311
  # Perform the request document validation required by driver specifications.
346
312
  # This method validates the first key of each update request document to be
347
313
  # an operator (i.e. start with $) and the first key of each replacement
data/lib/mongo/client.rb CHANGED
@@ -241,7 +241,11 @@ module Mongo
241
241
  # printed to the mongod logs upon establishing a connection
242
242
  # @option options [ Symbol ] :auth_mech The authentication mechanism to
243
243
  # use. One of :mongodb_cr, :mongodb_x509, :plain, :scram, :scram256
244
- # @option options [ Hash ] :auth_mech_properties
244
+ # @option options [ Hash ] :auth_mech_properties When passed in a
245
+ # connection string as authMechanismProperties, key-value pairs are
246
+ # delimited by commas, so a value containing a comma (",") must not be
247
+ # provided as part of the connection string. Such a value must be passed
248
+ # through this option instead. Values may contain colons (":").
245
249
  # @option options [ String ] :auth_source The source to authenticate from.
246
250
  # @option options [ true | false | nil | Integer ] :bg_error_backtrace
247
251
  # Experimental. Set to true to log complete backtraces for errors in
@@ -509,6 +513,10 @@ module Mongo
509
513
  # (this part of the API is subject to change).
510
514
  # - :encrypted_fields_map => Hash | nil, maps a collection namespace to
511
515
  # a hash describing encrypted fields for queryable encryption.
516
+ # - Note: Supplying an encrypted_fields_map provides more security
517
+ # than relying on an encryptedFields obtained from the server. It
518
+ # protects against a malicious server advertising a false
519
+ # encryptedFields.
512
520
  # - Note: If a collection is present on both the encryptedFieldsMap
513
521
  # and schemaMap, an error will be raised.
514
522
  # - :bypass_query_analysis => Boolean | nil, when true disables automatic
@@ -519,6 +527,10 @@ module Mongo
519
527
  # - :crypt_shared_lib_required => [ Boolean | nil ] Whether
520
528
  # crypt shared library is required. If 'true', an error will be raised
521
529
  # if a crypt_shared library cannot be loaded by libmongocrypt.
530
+ # - :key_expiration_ms => Integer | nil, the lifetime of the data
531
+ # encryption key cache, in milliseconds. Must be a non-negative
532
+ # integer. A value of 0 means the cache never expires. Defaults to
533
+ # 60000.
522
534
  #
523
535
  # Notes on automatic encryption:
524
536
  # - Automatic encryption is an enterprise only feature that only applies
@@ -601,9 +613,7 @@ module Mongo
601
613
  sdam_proc.call(self) if sdam_proc
602
614
 
603
615
  @connect_lock = Mutex.new
604
- @retry_policy = Retryable::RetryPolicy.new(
605
- max_retries: @options[:max_adaptive_retries] || Retryable::Backpressure::DEFAULT_MAX_RETRIES
606
- )
616
+ @retry_policy = build_retry_policy
607
617
  @connect_lock.synchronize do
608
618
  @cluster = Cluster.new(
609
619
  addresses,
@@ -621,7 +631,11 @@ module Mongo
621
631
  build_encrypter
622
632
  end
623
633
  end
624
- rescue StandardError
634
+ # ScriptError is rescued in addition to StandardError because
635
+ # Mongo::Crypt::Binding raises a LoadError when libmongocrypt cannot be
636
+ # found. Without it the cluster built above would be left open, leaking
637
+ # its monitoring and connection pool threads.
638
+ rescue StandardError, ScriptError
625
639
  begin
626
640
  @cluster.close
627
641
  rescue StandardError => e
@@ -830,6 +844,12 @@ module Mongo
830
844
  options.update(opts)
831
845
  @options = options.freeze
832
846
 
847
+ # The retry policy is built from the options, so a client created by
848
+ # #with needs its own policy when the option changed.
849
+ if @options[:max_adaptive_retries] != old_options[:max_adaptive_retries]
850
+ @retry_policy = build_retry_policy
851
+ end
852
+
833
853
  auto_encryption_options_changed =
834
854
  @options[:auto_encryption_options] != old_options[:auto_encryption_options]
835
855
 
@@ -1260,6 +1280,16 @@ module Mongo
1260
1280
 
1261
1281
  private
1262
1282
 
1283
+ # Builds the retry policy for the backpressure retry loops from the
1284
+ # client's options.
1285
+ #
1286
+ # @return [ Retryable::RetryPolicy ] The retry policy.
1287
+ def build_retry_policy
1288
+ Retryable::RetryPolicy.new(
1289
+ max_retries: @options[:max_adaptive_retries] || Retryable::Backpressure::DEFAULT_MAX_RETRIES
1290
+ )
1291
+ end
1292
+
1263
1293
  # Attempts to parse the given list of addresses, using the provided options.
1264
1294
  #
1265
1295
  # @param [ String | Array<String> ] addresses the list of addresses
@@ -42,6 +42,10 @@ module Mongo
42
42
  # @option options [ Integer ] :timeout_ms The operation timeout in milliseconds.
43
43
  # Must be a non-negative integer. An explicit value of 0 means infinite.
44
44
  # The default value is unset which means the feature is disabled.
45
+ # @option options [ Integer ] :key_expiration_ms The lifetime of the data
46
+ # encryption key cache, in milliseconds. Must be a non-negative integer.
47
+ # An explicit value of 0 means the cache never expires. The default is
48
+ # 60000.
45
49
  #
46
50
  # @raise [ ArgumentError ] If required options are missing or incorrectly
47
51
  # formatted.
@@ -50,7 +54,9 @@ module Mongo
50
54
  key_vault_client,
51
55
  options[:key_vault_namespace],
52
56
  Crypt::KMS::Credentials.new(options[:kms_providers]),
53
- Crypt::KMS::Validations.validate_tls_options(options[:kms_tls_options])
57
+ Crypt::KMS::Validations.validate_tls_options(options[:kms_tls_options]),
58
+ options[:timeout_ms],
59
+ options[:key_expiration_ms]
54
60
  )
55
61
  end
56
62
 
@@ -100,15 +106,34 @@ module Mongo
100
106
  # encryption key.
101
107
  # @option options [ String ] :algorithm The algorithm used to encrypt the value.
102
108
  # Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
103
- # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed".
109
+ # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "Range",
110
+ # "String".
104
111
  # @option options [ Integer | nil ] :contention_factor Contention factor
105
- # to be applied if encryption algorithm is set to "Indexed". If not
106
- # provided, it defaults to a value of 0. Contention factor should be set
107
- # only if encryption algorithm is set to "Indexed".
112
+ # to be applied if encryption algorithm is set to "Indexed", "Range", or
113
+ # "String". If not provided, it defaults to a value of 0. Contention factor
114
+ # should be set only if encryption algorithm is set to "Indexed", "Range",
115
+ # or "String".
108
116
  # @option options [ String | nil ] query_type Query type to be applied
109
- # if encryption algorithm is set to "Indexed". Query type should be set
110
- # only if encryption algorithm is set to "Indexed". The only allowed
111
- # value is "equality".
117
+ # if encryption algorithm is set to "Indexed", "Range", or "String".
118
+ # Allowed values are "equality" (for "Indexed"), "range" (for "Range"),
119
+ # and "prefix", "suffix", "substring" (for "String").
120
+ # @option options [ Hash | nil ] :range_opts Specifies index options for a
121
+ # Queryable Encryption field supporting "range" queries. Required when
122
+ # algorithm is "Range". Allowed options are :min, :max, :trim_factor,
123
+ # :sparsity, :precision.
124
+ # @option options [ Hash | nil ] :string_opts Specifies index options for a
125
+ # Queryable Encryption field supporting "prefix", "suffix", or "substring"
126
+ # queries. Required when algorithm is "String". Allowed options are
127
+ # :case_sensitive, :diacritic_sensitive, :prefix, :suffix, :substring.
128
+ #
129
+ # @note The result of explicit encryption with the "Indexed", "Range", or
130
+ # "String" algorithm must be processed by the server to insert or query.
131
+ # To insert or query with such a payload, use a Mongo::Client configured
132
+ # with :auto_encryption_options. The :bypass_query_analysis option may be
133
+ # true; the :bypass_auto_encryption option must be false.
134
+ #
135
+ # @note The "substring" query type is unstable and subject to backwards
136
+ # breaking changes.
112
137
  #
113
138
  # @note The :key_id and :key_alt_name options are mutually exclusive. Only
114
139
  # one is required to perform explicit encryption.
@@ -116,8 +141,8 @@ module Mongo
116
141
  # @return [ BSON::Binary ] A BSON Binary object of subtype 6 (ciphertext)
117
142
  # representing the encrypted value.
118
143
  #
119
- # @raise [ ArgumentError ] if either contention_factor or query_type
120
- # is set, and algorithm is not "Indexed".
144
+ # @raise [ ArgumentError ] if either contention_factor or query_type is set,
145
+ # and algorithm is not "Indexed", "Range", or "String".
121
146
  def encrypt(value, options = {})
122
147
  @encrypter.encrypt(value, options)
123
148
  end
@@ -148,7 +173,7 @@ module Mongo
148
173
  # expression. The only allowed value is "Range"
149
174
  # @option options [ Integer | nil ] :contention_factor Contention factor
150
175
  # to be applied If not provided, it defaults to a value of 0.
151
- # @option options [ String | nil ] query_type Query type to be applied.
176
+ # @option options [ String | nil ] :query_type Query type to be applied.
152
177
  # The only allowed value is "range".
153
178
  #
154
179
  # @note The :key_id and :key_alt_name options are mutually exclusive. Only
data/lib/mongo/cluster.rb CHANGED
@@ -586,8 +586,7 @@ module Mongo
586
586
  # @option options [ true | false ] :keep_connection_pool Usually when the
587
587
  # new server description is unknown, the connection pool on the
588
588
  # respective server is cleared. Set this option to true to keep the
589
- # existing connection pool (required when handling not master errors
590
- # on 4.2+ servers).
589
+ # existing connection pool (required when handling not master errors).
591
590
  # @option options [ true | false ] :awaited Whether the updated description
592
591
  # was a result of processing an awaited hello.
593
592
  # @option options [ Object ] :service_id Change state for the specified
@@ -62,8 +62,8 @@ module Mongo
62
62
  def maybe_drop_emm_collections(encrypted_fields, client, session)
63
63
  encrypted_fields = if encrypted_fields
64
64
  encrypted_fields
65
- elsif encrypted_fields_map
66
- encrypted_fields_for_drop_from_map
65
+ elsif client.options[:auto_encryption_options]
66
+ encrypted_fields_for_drop
67
67
  else
68
68
  {}
69
69
  end
@@ -127,12 +127,13 @@ module Mongo
127
127
  {}
128
128
  end
129
129
 
130
- # Tries to return the encrypted fields from the {{encrypted_fields_map}}
131
- # value, for the current namespace.
130
+ # Tries to return the encrypted fields for the current namespace from
131
+ # the {{encrypted_fields_map}} value, falling back to the encrypted
132
+ # fields advertised by the server for the collection.
132
133
  #
133
134
  # @return [ Hash | nil ] the encrypted fields, if found
134
- def encrypted_fields_for_drop_from_map
135
- encrypted_fields_map[namespace] ||
135
+ def encrypted_fields_for_drop
136
+ (encrypted_fields_map && encrypted_fields_map[namespace]) ||
136
137
  database.list_collections(filter: { name: name })
137
138
  .first
138
139
  &.fetch(:options, {})
@@ -88,11 +88,6 @@ module Mongo
88
88
  operation_timeouts: operation_timeouts(opts)
89
89
  )
90
90
  write_with_retry(write_concern, context: context) do |connection, txn_num, context|
91
- gte_4_4 = connection.server.description.server_version_gte?('4.4')
92
- if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
93
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
94
- end
95
-
96
91
  Operation::WriteCommand.new(
97
92
  selector: cmd,
98
93
  db_name: database.name,
@@ -216,11 +211,6 @@ module Mongo
216
211
  )
217
212
  value = tracer.trace_operation(operation, context, op_name: 'findOneAndUpdate') do
218
213
  write_with_retry(write_concern, context: context) do |connection, txn_num, context|
219
- gte_4_4 = connection.server.description.server_version_gte?('4.4')
220
- if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
221
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
222
- end
223
-
224
214
  operation.txn_num = txn_num
225
215
  operation.execute_with_connection(connection, context: context)
226
216
  end
@@ -287,11 +277,6 @@ module Mongo
287
277
  )
288
278
  tracer.trace_operation(operation, context, op_name: 'deleteMany') do
289
279
  nro_write_with_retry(write_concern, context: context) do |connection, _txn_num, context|
290
- gte_4_4 = connection.server.description.server_version_gte?('4.4')
291
- if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
292
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
293
- end
294
-
295
280
  operation.execute_with_connection(connection, context: context)
296
281
  end
297
282
  end
@@ -346,11 +331,6 @@ module Mongo
346
331
  operation_timeouts: operation_timeouts(opts)
347
332
  )
348
333
  write_with_retry(write_concern, context: context) do |connection, txn_num, context|
349
- gte_4_4 = connection.server.description.server_version_gte?('4.4')
350
- if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
351
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
352
- end
353
-
354
334
  Operation::Delete.new(
355
335
  deletes: [ delete_doc ],
356
336
  db_name: collection.database.name,
@@ -428,11 +408,6 @@ module Mongo
428
408
  operation_timeouts: operation_timeouts(opts)
429
409
  )
430
410
  write_with_retry(write_concern, context: context) do |connection, txn_num, context|
431
- gte_4_2 = connection.server.description.server_version_gte?('4.2')
432
- if !gte_4_2 && opts[:hint] && write_concern && !write_concern.acknowledged?
433
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
434
- end
435
-
436
411
  Operation::Update.new(
437
412
  updates: [ update_doc ],
438
413
  db_name: collection.database.name,
@@ -507,11 +482,6 @@ module Mongo
507
482
  operation_timeouts: operation_timeouts(opts)
508
483
  )
509
484
  nro_write_with_retry(write_concern, context: context) do |connection, _txn_num, context|
510
- gte_4_2 = connection.server.description.server_version_gte?('4.2')
511
- if !gte_4_2 && opts[:hint] && write_concern && !write_concern.acknowledged?
512
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
513
- end
514
-
515
485
  Operation::Update.new(
516
486
  updates: [ update_doc ],
517
487
  db_name: collection.database.name,
@@ -601,11 +571,6 @@ module Mongo
601
571
  )
602
572
  tracer.trace_operation(operation, context) do
603
573
  write_with_retry(write_concern, context: context) do |connection, txn_num, context|
604
- gte_4_2 = connection.server.description.server_version_gte?('4.2')
605
- if !gte_4_2 && opts[:hint] && write_concern && !write_concern.acknowledged?
606
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
607
- end
608
-
609
574
  operation.txn_num = txn_num
610
575
  operation.execute_with_connection(connection, context: context)
611
576
  end
@@ -81,6 +81,9 @@ module Mongo
81
81
  # @option options [ Boolean | nil ] :crypt_shared_lib_required Whether
82
82
  # crypt shared library is required. If 'true', an error will be raised
83
83
  # if a crypt_shared library cannot be loaded by libmongocrypt.
84
+ # @option options [ Integer | nil ] :key_expiration_ms The lifetime of the
85
+ # data encryption key cache, in milliseconds. A value of 0 means the
86
+ # cache never expires. Defaults to 60000.
84
87
  #
85
88
  # @raise [ ArgumentError ] If required options are missing or incorrectly
86
89
  # formatted.
@@ -99,7 +102,8 @@ module Mongo
99
102
  bypass_query_analysis: @options[:bypass_query_analysis],
100
103
  crypt_shared_lib_path: @options[:extra_options][:crypt_shared_lib_path],
101
104
  crypt_shared_lib_required: @options[:extra_options][:crypt_shared_lib_required],
102
- disable_crypt_shared_lib_search: @options[:extra_options][:disable_crypt_shared_lib_search]
105
+ disable_crypt_shared_lib_search: @options[:extra_options][:disable_crypt_shared_lib_search],
106
+ key_expiration_ms: @options[:key_expiration_ms]
103
107
  )
104
108
 
105
109
  @mongocryptd_options = @options[:extra_options].slice(
@@ -81,7 +81,7 @@ module Mongo
81
81
  # will cause a `LoadError`.
82
82
  #
83
83
  # @api private
84
- MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new('1.12.0')
84
+ MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new('1.20.0')
85
85
 
86
86
  # @!method self.mongocrypt_version(len)
87
87
  # @api private
@@ -1479,6 +1479,31 @@ module Mongo
1479
1479
  mongocrypt_setopt_bypass_query_analysis(handle.ref)
1480
1480
  end
1481
1481
 
1482
+ # @!method self.mongocrypt_setopt_key_expiration(crypt, cache_expiration_ms)
1483
+ # @api private
1484
+ #
1485
+ # Set the expiration time for the data encryption key cache.
1486
+ #
1487
+ # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
1488
+ # @param [ Integer ] cache_expiration_ms The cache expiration time in
1489
+ # milliseconds. If zero, the cache never expires.
1490
+ # @return [ Boolean ] Returns whether the option was set successfully.
1491
+ attach_function(:mongocrypt_setopt_key_expiration, %i[pointer uint64], :bool)
1492
+
1493
+ # Set the expiration time for the data encryption key cache on the
1494
+ # Mongo::Crypt::Handle object.
1495
+ #
1496
+ # @param [ Mongo::Crypt::Handle ] handle
1497
+ # @param [ Integer ] cache_expiration_ms The cache expiration time in
1498
+ # milliseconds. If zero, the cache never expires.
1499
+ #
1500
+ # @raise [ Mongo::Error::CryptError ] If the option is not set successfully.
1501
+ def self.setopt_key_expiration(handle, cache_expiration_ms)
1502
+ check_status(handle) do
1503
+ mongocrypt_setopt_key_expiration(handle.ref, cache_expiration_ms)
1504
+ end
1505
+ end
1506
+
1482
1507
  # @!method self.mongocrypt_setopt_aes_256_ctr(crypt, aes_256_ctr_encrypt, aes_256_ctr_decrypt, ctx)
1483
1508
  # @api private
1484
1509
  #
@@ -1873,6 +1898,46 @@ module Mongo
1873
1898
  end
1874
1899
  end
1875
1900
 
1901
+ # @!method self.mongocrypt_ctx_setopt_algorithm_text(ctx, opts)
1902
+ # @api private
1903
+ #
1904
+ # Set options for explicit encryption with the "String" algorithm.
1905
+ #
1906
+ # @note The libmongocrypt C function is named `..._algorithm_text` (the
1907
+ # original "text" name), even though the algorithm string passed to
1908
+ # mongocrypt_ctx_setopt_algorithm is "String" (renamed for GA). There is
1909
+ # no `..._algorithm_string` symbol; `..._algorithm_text` is the correct
1910
+ # and only setter for these options.
1911
+ #
1912
+ # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object.
1913
+ # @param [ FFI::Pointer ] opts A pointer to a string options document.
1914
+ #
1915
+ # @return [ Boolean ] Whether setting this option succeeded.
1916
+ attach_function(
1917
+ :mongocrypt_ctx_setopt_algorithm_text,
1918
+ %i[
1919
+ pointer
1920
+ pointer
1921
+ ],
1922
+ :bool
1923
+ )
1924
+
1925
+ # Set options for explicit encryption with the "String" algorithm.
1926
+ #
1927
+ # @param [ Mongo::Crypt::Context ] context
1928
+ # @param [ Hash ] opts options
1929
+ #
1930
+ # @raise [ Mongo::Error::CryptError ] If the operation failed
1931
+ def self.ctx_setopt_algorithm_text(context, opts)
1932
+ validate_document(opts)
1933
+ data = opts.to_bson.to_s
1934
+ Binary.wrap_string(data) do |data_p|
1935
+ check_ctx_status(context) do
1936
+ mongocrypt_ctx_setopt_algorithm_text(context.ctx_p, data_p)
1937
+ end
1938
+ end
1939
+ end
1940
+
1876
1941
  # Raise a Mongo::Error::CryptError based on the status of the underlying
1877
1942
  # mongocrypt_t object.
1878
1943
  #
@@ -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|
@@ -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
@@ -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,
@@ -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)
@@ -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,7 @@ module Mongo
130
130
  doc[:driver] = driver_doc
131
131
  doc[:os] = os_doc
132
132
  doc[:platform] = platform_string
133
- doc[:backpressure] = true
133
+ doc[:backpressure] = '2'
134
134
  env_doc.tap { |env| doc[:env] = env if env }
135
135
  end
136
136
  end
@@ -24,7 +24,6 @@ module Mongo
24
24
  # List of features and the wire protocol version they appear in.
25
25
  #
26
26
  # Wire protocol versions map to server releases as follows:
27
- # - 8 => 4.2
28
27
  # - 9 => 4.4
29
28
  # - 13 => 5.0
30
29
  # - 14 => 5.1
@@ -61,7 +60,7 @@ module Mongo
61
60
  # The wire protocol versions that this version of the driver supports.
62
61
  #
63
62
  # @since 2.0.0
64
- DRIVER_WIRE_VERSIONS = 8..29
63
+ DRIVER_WIRE_VERSIONS = 9..29
65
64
 
66
65
  # The wire protocol versions that are deprecated in this version of the
67
66
  # driver. Support for these versions will be removed in the future.
@@ -74,7 +73,7 @@ module Mongo
74
73
  #
75
74
  # If there are no currently-deprecated wire versions, this should be
76
75
  # set to an empty range (e.g. the EMPTY_RANGE constant).
77
- DEPRECATED_WIRE_VERSIONS = 8..8
76
+ DEPRECATED_WIRE_VERSIONS = EMPTY_RANGE
78
77
 
79
78
  # make sure the deprecated versions are valid
80
79
  if DEPRECATED_WIRE_VERSIONS.min && (DRIVER_WIRE_VERSIONS.min > DEPRECATED_WIRE_VERSIONS.max)
@@ -905,7 +905,6 @@ module Mongo
905
905
  '5.1' => 14,
906
906
  '5.0' => 12,
907
907
  '4.4' => 9,
908
- '4.2' => 8,
909
908
  }.freeze
910
909
 
911
910
  # @api private
@@ -52,11 +52,6 @@ module Mongo
52
52
  # SCRAM-SHA-1. The use of SCRAM-SHA-256 by default is thus
53
53
  # sensible, and it is also mandated by the speculative auth spec.
54
54
  #
55
- # If no mechanism was specified and we are talking to a 4.2
56
- # server, we'll send speculative auth document, the server will
57
- # ignore it and we'll perform authentication using explicit
58
- # command.
59
- #
60
55
  # If no mechanism was specified and we are talking to a 4.4+
61
56
  # server and the user account doesn't allow SCRAM-SHA-256, we will
62
57
  # authenticate in a separate command with SCRAM-SHA-1 after
data/lib/mongo/server.rb CHANGED
@@ -567,8 +567,7 @@ module Mongo
567
567
  # @option options [ true | false ] :keep_connection_pool Usually when the
568
568
  # new server description is unknown, the connection pool on the
569
569
  # respective server is cleared. Set this option to true to keep the
570
- # existing connection pool (required when handling not master errors
571
- # on 4.2+ servers).
570
+ # existing connection pool (required when handling not master errors).
572
571
  # @option options [ TopologyVersion ] :topology_version Topology version
573
572
  # of the error response that is causing the server to be marked unknown.
574
573
  # @option options [ true | false ] :stop_push_monitor Set when the server
data/lib/mongo/session.rb CHANGED
@@ -471,7 +471,7 @@ module Mongo
471
471
  loop do
472
472
  if transaction_attempt > 0
473
473
  if overload_encountered
474
- delay = @client.retry_policy.backoff_delay(overload_error_count)
474
+ delay = @client.retry_policy.backoff_delay(overload_error_count, err: last_error)
475
475
  if backoff_would_exceed_deadline?(deadline, delay)
476
476
  make_timeout_error_from(last_error, 'CSOT timeout expired waiting to retry withTransaction')
477
477
  end
@@ -562,7 +562,7 @@ module Mongo
562
562
  end
563
563
 
564
564
  if overload_encountered
565
- delay = @client.retry_policy.backoff_delay(overload_error_count)
565
+ delay = @client.retry_policy.backoff_delay(overload_error_count, err: e)
566
566
  if backoff_would_exceed_deadline?(deadline, delay)
567
567
  transaction_in_progress = false
568
568
  make_timeout_error_from(e, 'CSOT timeout expired during withTransaction commit')
@@ -1429,7 +1429,7 @@ module Mongo
1429
1429
  private_constant :BACKOFF_INITIAL, :BACKOFF_MAX
1430
1430
 
1431
1431
  def backoff_seconds_for_retry(transaction_attempt)
1432
- exponential = BACKOFF_INITIAL * (1.5**(transaction_attempt - 1))
1432
+ exponential = BACKOFF_INITIAL * (1.5**transaction_attempt)
1433
1433
  Random.rand * [ exponential, BACKOFF_MAX ].min
1434
1434
  end
1435
1435
 
@@ -614,7 +614,7 @@ module Mongo
614
614
  #
615
615
  # @return [ Hash | nil ] The auth mechanism properties hash.
616
616
  def convert_auth_mech_props(_name, value)
617
- properties = hash_extractor('authMechanismProperties', value)
617
+ properties = hash_extractor('authMechanismProperties', value, invalidate_on_error: true)
618
618
  if properties
619
619
  properties.each do |k, v|
620
620
  properties[k] = (v.downcase == 'true') if k.to_s.downcase == 'canonicalize_host_name' && v
@@ -863,16 +863,27 @@ module Mongo
863
863
 
864
864
  # Extract values from the string and put them into a nested hash.
865
865
  #
866
+ # Multiple key-value pairs are delimited by a comma. Within each pair,
867
+ # the key is everything up to the first colon and the value is everything
868
+ # after it, so values may themselves contain colons (for example
869
+ # TOKEN_RESOURCE:mongodb://host).
870
+ #
866
871
  # @param [ String ] name Name of the URI option being processed.
867
872
  # @param [ String ] value The string to build a hash from.
873
+ # @param [ true | false ] invalidate_on_error When a pair is malformed
874
+ # (has no value), discard the whole option and return nil instead of
875
+ # skipping just that pair. Used for options such as
876
+ # authMechanismProperties whose values must not contain commas.
868
877
  #
869
- # @return [ Hash ] The hash built from the string.
870
- def hash_extractor(name, value)
878
+ # @return [ Hash | nil ] The hash built from the string, or nil.
879
+ def hash_extractor(name, value, invalidate_on_error: false)
871
880
  h = {}
872
881
  value.split(',').each do |tag|
873
- k, v = tag.split(':')
882
+ k, v = tag.split(':', 2)
874
883
  if v.nil?
875
884
  log_warn("Invalid hash value for #{name}: key `#{k}` does not have a value: #{value}")
885
+ return nil if invalidate_on_error
886
+
876
887
  next
877
888
  end
878
889
 
data/lib/mongo/version.rb CHANGED
@@ -5,5 +5,5 @@ module Mongo
5
5
  #
6
6
  # Note that this file is automatically updated via `rake candidate:create`.
7
7
  # Manual changes to this file will be overwritten by that rake task.
8
- VERSION = '2.25.0'
8
+ VERSION = '2.26.0'
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.25.0
4
+ version: 2.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - The MongoDB Ruby Team
@@ -561,7 +561,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
561
561
  - !ruby/object:Gem::Version
562
562
  version: '0'
563
563
  requirements: []
564
- rubygems_version: 4.0.17
564
+ rubygems_version: 4.0.20
565
565
  specification_version: 4
566
566
  summary: Ruby driver for MongoDB
567
567
  test_files: []