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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 846c673e8e85d3e7007bec794833276ed4da9591347e361465b190c10829c504
4
- data.tar.gz: ad93e6b640fbdb3c6f757df0c0ae433f4c79d50c209fb0bd3c0b8c84402148a9
3
+ metadata.gz: 39d9792c95b149dbbcb1b065df222c345eea9c84912e03de9a8994d7aea32ab8
4
+ data.tar.gz: b491401e88ffde8324e802267b35f75f6f92997f5746b3634612e1a9d63f11e8
5
5
  SHA512:
6
- metadata.gz: 29750ebefccf852b621847e68bffd039f3e28c63829334880416d7e8348fbd8cc5769d7f5b1cf872030d874610739ef4df90f493d9b325d8bebcfe2dd7d7b638
7
- data.tar.gz: fbb6248041e00a490ccf23e62c364ce3c8ea8214f3fd4c6f07ff8df18e090702c53039e30a1b2b66f8a5e464ebf4e9162d993a0c67cd0818911449a91d168b3f
6
+ metadata.gz: aad7e612834dd20849a707f6fde3d5a8101765af93df37eee119ad0418dd518b738ec8834689f0aeac267ac117aab2f89f84362f0c588f8714b96c1c87c286ef
7
+ data.tar.gz: 6a1c05b23598ac63e37267393de256bd0f2d250f14e881e2fda7a436a2ee2775d60e61d076c4fbaaa77230e7135f89095c55a737edca13a3434f048fdf8aa5db
@@ -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, {})
@@ -115,15 +115,15 @@ module Mongo
115
115
 
116
116
  def send_initial_query(server, context, operation: nil)
117
117
  if server.load_balancer?
118
- # Connection will be checked in when cursor is drained.
119
- connection = server.pool.check_out(context: context)
120
- initial_query_op(
121
- context.session,
122
- effective_read_preference(connection)
123
- ).execute_with_connection(
124
- connection,
125
- context: context
126
- )
118
+ server.pool.with_cursor_connection(context: context) do |connection|
119
+ initial_query_op(
120
+ context.session,
121
+ effective_read_preference(connection)
122
+ ).execute_with_connection(
123
+ connection,
124
+ context: context
125
+ )
126
+ end
127
127
  else
128
128
  server.with_connection do |connection|
129
129
  initial_query_op(
@@ -350,8 +350,7 @@ module Mongo
350
350
  if server.load_balancer?
351
351
  # In load balanced topology, manually check out a connection
352
352
  # so it remains checked out and pinned to the cursor.
353
- connection = server.pool.check_out(context: context)
354
- begin
353
+ server.pool.with_cursor_connection(context: context) do |connection|
355
354
  result = send_initial_query(connection, context)
356
355
 
357
356
  start_at_operation_time = if (doc = result.replies.first && result.replies.first.documents.first)
@@ -360,9 +359,6 @@ module Mongo
360
359
  nil
361
360
  end
362
361
  result
363
- rescue StandardError
364
- server.pool.check_in(connection)
365
- raise
366
362
  end
367
363
  else
368
364
  server.with_connection do |connection|
@@ -172,16 +172,9 @@ module Mongo
172
172
  def send_initial_query(server, context, operation: nil)
173
173
  operation ||= initial_query_op(context.session)
174
174
  if server.load_balancer?
175
- # Connection will be checked in when cursor is drained,
176
- # unless the connection is pinned to a transaction (in which
177
- # case it stays checked out for the transaction duration).
178
- if context.connection_global_id
179
- connection = server.pool.check_out_pinned_connection(
180
- context.connection_global_id
181
- )
175
+ server.pool.with_cursor_connection(context: context) do |connection|
176
+ operation.execute_with_connection(connection, context: context)
182
177
  end
183
- connection ||= server.pool.check_out(context: context)
184
- operation.execute_with_connection(connection, context: context)
185
178
  else
186
179
  operation.execute(server, context: context)
187
180
  end
@@ -73,10 +73,11 @@ module Mongo
73
73
  context = Operation::Context.new(client: client, session: session,
74
74
  operation_timeouts: view.operation_timeouts)
75
75
  if server.load_balancer?
76
- # Connection will be checked in when cursor is drained.
77
- connection = server.pool.check_out(context: context)
78
- result = send_initial_query_with_connection(connection, context.session, context: context)
79
- result = send_fetch_query_with_connection(connection, session) unless inline?
76
+ result = server.pool.with_cursor_connection(context: context) do |connection|
77
+ res = send_initial_query_with_connection(connection, context.session, context: context)
78
+ res = send_fetch_query_with_connection(connection, session) unless inline?
79
+ res
80
+ end
80
81
  else
81
82
  result = send_initial_query(server, context)
82
83
  result = send_fetch_query(server, session) unless inline?
@@ -739,9 +739,9 @@ module Mongo
739
739
  connection_global_id: result.connection_global_id
740
740
  )
741
741
  result = if server.load_balancer?
742
- # Connection will be checked in when cursor is drained.
743
- connection = server.pool.check_out(context: context)
744
- op.execute_with_connection(connection, context: context)
742
+ server.pool.with_cursor_connection(context: context) do |connection|
743
+ op.execute_with_connection(connection, context: context)
744
+ end
745
745
  else
746
746
  op.execute(server, context: context)
747
747
  end
@@ -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
  #