mongo 2.24.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mongo_console +2 -2
  3. data/lib/mongo/address/validator.rb +1 -1
  4. data/lib/mongo/address.rb +1 -3
  5. data/lib/mongo/auth/aws/conversation.rb +0 -4
  6. data/lib/mongo/auth/aws/credentials_retriever.rb +3 -8
  7. data/lib/mongo/auth/base.rb +1 -1
  8. data/lib/mongo/auth/sasl_conversation_base.rb +1 -1
  9. data/lib/mongo/auth/scram_conversation_base.rb +1 -8
  10. data/lib/mongo/auth/stringprep.rb +1 -6
  11. data/lib/mongo/auth/user/view.rb +1 -1
  12. data/lib/mongo/bulk_write/result.rb +10 -2
  13. data/lib/mongo/bulk_write/result_combiner.rb +11 -4
  14. data/lib/mongo/bulk_write.rb +1 -1
  15. data/lib/mongo/client.rb +82 -12
  16. data/lib/mongo/cluster/reapers/cursor_reaper.rb +12 -5
  17. data/lib/mongo/cluster/sdam_flow.rb +4 -4
  18. data/lib/mongo/cluster/topology/base.rb +1 -1
  19. data/lib/mongo/cluster.rb +12 -28
  20. data/lib/mongo/collection/view/aggregation/behavior.rb +1 -1
  21. data/lib/mongo/collection/view/aggregation.rb +2 -4
  22. data/lib/mongo/collection/view/builder/aggregation.rb +3 -5
  23. data/lib/mongo/collection/view/builder/map_reduce.rb +1 -1
  24. data/lib/mongo/collection/view/change_stream.rb +14 -9
  25. data/lib/mongo/collection/view/map_reduce.rb +2 -11
  26. data/lib/mongo/collection/view/readable.rb +11 -7
  27. data/lib/mongo/collection/view/writable.rb +4 -4
  28. data/lib/mongo/collection/view.rb +8 -4
  29. data/lib/mongo/collection.rb +1 -1
  30. data/lib/mongo/config.rb +5 -0
  31. data/lib/mongo/crypt/auto_encrypter.rb +1 -1
  32. data/lib/mongo/crypt/binary.rb +7 -4
  33. data/lib/mongo/crypt/binding.rb +88 -10
  34. data/lib/mongo/crypt/context.rb +50 -19
  35. data/lib/mongo/crypt/encryption_io.rb +11 -5
  36. data/lib/mongo/crypt/handle.rb +17 -5
  37. data/lib/mongo/crypt/kms/credentials.rb +70 -31
  38. data/lib/mongo/crypt/kms/kmip/master_document.rb +30 -1
  39. data/lib/mongo/crypt/kms/master_key_document.rb +11 -6
  40. data/lib/mongo/crypt/kms.rb +12 -1
  41. data/lib/mongo/cursor.rb +8 -2
  42. data/lib/mongo/database/cursor_command_view.rb +95 -0
  43. data/lib/mongo/database.rb +144 -18
  44. data/lib/mongo/error/bulk_write_error.rb +29 -1
  45. data/lib/mongo/error/invalid_server_preference.rb +3 -0
  46. data/lib/mongo/error/invalid_uri.rb +5 -1
  47. data/lib/mongo/error/operation_failure.rb +42 -3
  48. data/lib/mongo/error/parser.rb +3 -3
  49. data/lib/mongo/error/sdam_error_detection.rb +2 -3
  50. data/lib/mongo/grid/fs_bucket.rb +27 -21
  51. data/lib/mongo/grid/stream/write.rb +2 -2
  52. data/lib/mongo/index/view.rb +13 -7
  53. data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +1 -0
  54. data/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb +1 -0
  55. data/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +1 -0
  56. data/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +1 -0
  57. data/lib/mongo/monitoring/event/cmap/connection_closed.rb +1 -0
  58. data/lib/mongo/monitoring/event/cmap/connection_created.rb +1 -0
  59. data/lib/mongo/monitoring/event/cmap/connection_ready.rb +1 -0
  60. data/lib/mongo/monitoring/event/cmap/pool_cleared.rb +1 -0
  61. data/lib/mongo/monitoring/event/cmap/pool_closed.rb +1 -0
  62. data/lib/mongo/monitoring/event/cmap/pool_created.rb +1 -0
  63. data/lib/mongo/monitoring/event/cmap/pool_ready.rb +1 -0
  64. data/lib/mongo/monitoring/event/command_failed.rb +1 -0
  65. data/lib/mongo/monitoring/event/command_started.rb +1 -0
  66. data/lib/mongo/monitoring/event/command_succeeded.rb +1 -0
  67. data/lib/mongo/monitoring/event/secure.rb +12 -17
  68. data/lib/mongo/monitoring/event/server_closed.rb +1 -0
  69. data/lib/mongo/monitoring/event/server_description_changed.rb +1 -0
  70. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +1 -0
  71. data/lib/mongo/monitoring/event/server_heartbeat_started.rb +1 -0
  72. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +1 -0
  73. data/lib/mongo/monitoring/event/server_opening.rb +1 -0
  74. data/lib/mongo/monitoring/event/topology_changed.rb +1 -0
  75. data/lib/mongo/monitoring/event/topology_closed.rb +1 -0
  76. data/lib/mongo/monitoring/event/topology_opening.rb +1 -0
  77. data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +2 -0
  78. data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +3 -0
  79. data/lib/mongo/operation/create/op_msg.rb +1 -0
  80. data/lib/mongo/operation/create_index/op_msg.rb +2 -1
  81. data/lib/mongo/operation/cursor_command/op_msg.rb +37 -0
  82. data/lib/mongo/operation/cursor_command/result.rb +60 -0
  83. data/lib/mongo/operation/cursor_command.rb +33 -0
  84. data/lib/mongo/operation/delete/op_msg.rb +1 -0
  85. data/lib/mongo/operation/drop/op_msg.rb +1 -0
  86. data/lib/mongo/operation/drop_database/op_msg.rb +1 -0
  87. data/lib/mongo/operation/drop_index/op_msg.rb +1 -0
  88. data/lib/mongo/operation/find/builder/command.rb +1 -1
  89. data/lib/mongo/operation/insert/bulk_result.rb +2 -4
  90. data/lib/mongo/operation/insert/op_msg.rb +1 -0
  91. data/lib/mongo/operation/result.rb +14 -26
  92. data/lib/mongo/operation/shared/causal_consistency_supported.rb +4 -2
  93. data/lib/mongo/operation/shared/executable.rb +2 -10
  94. data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
  95. data/lib/mongo/operation/shared/sessions_supported.rb +3 -5
  96. data/lib/mongo/operation/update/bulk_result.rb +1 -1
  97. data/lib/mongo/operation/update/op_msg.rb +1 -0
  98. data/lib/mongo/operation/write_command/op_msg.rb +2 -0
  99. data/lib/mongo/operation.rb +1 -0
  100. data/lib/mongo/options/redacted.rb +2 -2
  101. data/lib/mongo/protocol/compressed.rb +15 -11
  102. data/lib/mongo/protocol/message.rb +16 -16
  103. data/lib/mongo/protocol/registry.rb +1 -1
  104. data/lib/mongo/protocol/serializers.rb +1 -3
  105. data/lib/mongo/query_cache.rb +3 -5
  106. data/lib/mongo/server/connection.rb +3 -1
  107. data/lib/mongo/server/connection_common.rb +9 -1
  108. data/lib/mongo/server/connection_pool/generation_manager.rb +1 -1
  109. data/lib/mongo/server/connection_pool.rb +79 -18
  110. data/lib/mongo/server/description/features.rb +2 -2
  111. data/lib/mongo/server/description.rb +24 -2
  112. data/lib/mongo/server/monitor/connection.rb +3 -2
  113. data/lib/mongo/server/monitor.rb +133 -30
  114. data/lib/mongo/server/pending_connection.rb +1 -0
  115. data/lib/mongo/server/push_monitor.rb +16 -2
  116. data/lib/mongo/server.rb +27 -7
  117. data/lib/mongo/server_selector/base.rb +16 -6
  118. data/lib/mongo/server_selector/nearest.rb +3 -0
  119. data/lib/mongo/server_selector/primary.rb +3 -0
  120. data/lib/mongo/server_selector/primary_preferred.rb +3 -0
  121. data/lib/mongo/server_selector/secondary.rb +3 -0
  122. data/lib/mongo/server_selector/secondary_preferred.rb +7 -1
  123. data/lib/mongo/session.rb +56 -24
  124. data/lib/mongo/socket/ssl.rb +3 -3
  125. data/lib/mongo/socket.rb +1 -7
  126. data/lib/mongo/srv/monitor.rb +11 -2
  127. data/lib/mongo/srv/resolver.rb +1 -1
  128. data/lib/mongo/srv/result.rb +5 -3
  129. data/lib/mongo/timeout.rb +10 -15
  130. data/lib/mongo/tracing/open_telemetry/command_tracer.rb +43 -2
  131. data/lib/mongo/uri/options_mapper.rb +21 -9
  132. data/lib/mongo/uri/srv_protocol.rb +4 -4
  133. data/lib/mongo/uri.rb +56 -8
  134. data/lib/mongo/utils.rb +1 -1
  135. data/lib/mongo/version.rb +1 -1
  136. data/lib/mongo.rb +1 -0
  137. metadata +6 -4
  138. data/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +0 -173
  139. data/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +0 -1164
@@ -351,19 +351,24 @@ module Mongo
351
351
  # In load balanced topology, manually check out a connection
352
352
  # so it remains checked out and pinned to the cursor.
353
353
  connection = server.pool.check_out(context: context)
354
- result = send_initial_query(connection, context)
355
-
356
- start_at_operation_time = if doc = result.replies.first && result.replies.first.documents.first
357
- doc['operationTime']
358
- else
359
- nil
360
- end
361
- result
354
+ begin
355
+ result = send_initial_query(connection, context)
356
+
357
+ start_at_operation_time = if (doc = result.replies.first && result.replies.first.documents.first)
358
+ doc['operationTime']
359
+ else
360
+ nil
361
+ end
362
+ result
363
+ rescue StandardError
364
+ server.pool.check_in(connection)
365
+ raise
366
+ end
362
367
  else
363
368
  server.with_connection do |connection|
364
369
  result = send_initial_query(connection, context)
365
370
 
366
- start_at_operation_time = if doc = result.replies.first && result.replies.first.documents.first
371
+ start_at_operation_time = if (doc = result.replies.first && result.replies.first.documents.first)
367
372
  doc['operationTime']
368
373
  else
369
374
  # The above may set @start_at_operation_time to nil
@@ -227,7 +227,7 @@ module Mongo
227
227
  #
228
228
  # @since 2.5.0
229
229
  def execute
230
- view.send(:with_session, @options) do |session|
230
+ view.with_session(@options) do |session|
231
231
  write_concern = view.write_concern_with_session(session)
232
232
  context = Operation::Context.new(client: client, session: session)
233
233
  nro_write_with_retry(write_concern, context: context) do |connection, _txn_num, context|
@@ -241,7 +241,7 @@ module Mongo
241
241
  OUT_ACTIONS = %i[replace merge reduce].freeze
242
242
 
243
243
  def server_selector
244
- @view.send(:server_selector)
244
+ @view.server_selector
245
245
  end
246
246
 
247
247
  def inline?
@@ -299,15 +299,6 @@ module Mongo
299
299
  end
300
300
  end
301
301
 
302
- def fetch_query_spec
303
- Builder::MapReduce.new(map_function, reduce_function, view, options).query_specification
304
- end
305
-
306
- def find_command_spec(session)
307
- Builder::MapReduce.new(map_function, reduce_function, view,
308
- options.merge(session: session)).command_specification
309
- end
310
-
311
302
  def fetch_query_op(session)
312
303
  spec = {
313
304
  coll_name: out_collection_name,
@@ -678,7 +678,7 @@ module Mongo
678
678
  # @api private
679
679
  def read_concern
680
680
  if options[:session] && options[:session].in_transaction?
681
- options[:session].send(:txn_read_concern) || collection.client.read_concern
681
+ options[:session].txn_read_concern || collection.client.read_concern
682
682
  else
683
683
  collection.read_concern
684
684
  end
@@ -749,12 +749,10 @@ module Mongo
749
749
  end
750
750
  end
751
751
 
752
- private
753
-
754
- def collation(doc = nil)
755
- configure(:collation, doc)
756
- end
757
-
752
+ # The server selector for this view, derived from its read
753
+ # preference (or the collection/client default).
754
+ #
755
+ # @api private
758
756
  def server_selector
759
757
  @server_selector ||= if options[:session] && options[:session].in_transaction?
760
758
  ServerSelector.get(read_preference || client.server_selector)
@@ -763,6 +761,12 @@ module Mongo
763
761
  end
764
762
  end
765
763
 
764
+ private
765
+
766
+ def collation(doc = nil)
767
+ configure(:collation, doc)
768
+ end
769
+
766
770
  def validate_doc!(doc)
767
771
  raise Error::InvalidDocument.new unless doc.respond_to?(:keys)
768
772
  end
@@ -624,8 +624,8 @@ module Mongo
624
624
  # @raise [ Error::InvalidUpdateDocument ] if the first key in the
625
625
  # document does not start with a $.
626
626
  def validate_update_documents!(spec)
627
- return unless update = spec.is_a?(Array) ? spec&.first : spec
628
- return unless key = update.keys&.first
627
+ return unless (update = spec.is_a?(Array) ? spec&.first : spec)
628
+ return unless (key = update.keys&.first)
629
629
  return if key.to_s.start_with?('$')
630
630
  raise Error::InvalidUpdateDocument.new(key: key) if Mongo.validate_update_replace
631
631
 
@@ -641,8 +641,8 @@ module Mongo
641
641
  # @raise [ Error::InvalidUpdateDocument ] if the first key in the
642
642
  # document does not start with a $.
643
643
  def validate_replacement_documents!(spec)
644
- return unless replace = spec.is_a?(Array) ? spec&.first : spec
645
- return unless key = replace.keys&.first
644
+ return unless (replace = spec.is_a?(Array) ? spec&.first : spec)
645
+ return unless (key = replace.keys&.first)
646
646
  return unless key.to_s.start_with?('$')
647
647
  raise Error::InvalidReplacementDocument.new(key: key) if Mongo.validate_update_replace
648
648
 
@@ -236,6 +236,14 @@ module Mongo
236
236
  end
237
237
  end
238
238
 
239
+ # Executes the provided block within the context of a session, using
240
+ # this view's options merged with the given ones.
241
+ #
242
+ # @api private
243
+ def with_session(opts = {}, &block)
244
+ client.with_session(@options.merge(opts), &block)
245
+ end
246
+
239
247
  private
240
248
 
241
249
  def initialize_copy(other)
@@ -252,10 +260,6 @@ module Mongo
252
260
  def view
253
261
  self
254
262
  end
255
-
256
- def with_session(opts = {}, &block)
257
- client.with_session(@options.merge(opts), &block)
258
- end
259
263
  end
260
264
  end
261
265
  end
@@ -393,7 +393,7 @@ module Mongo
393
393
  operation = { create: name }.merge(options)
394
394
  operation.delete(:write)
395
395
  operation.delete(:write_concern)
396
- client.send(:with_session, opts) do |session|
396
+ client.with_session(opts) do |session|
397
397
  write_concern = if opts[:write_concern]
398
398
  WriteConcern.get(opts[:write_concern])
399
399
  else
data/lib/mongo/config.rb CHANGED
@@ -29,6 +29,11 @@ module Mongo
29
29
  # decryption instead of BSON types.
30
30
  option :csfle_convert_to_ruby_types, default: false
31
31
 
32
+ # When this flag is set to true, the (host:port) of the server that produced
33
+ # the error is appended to error messages for OperationFailure and
34
+ # BulkWriteError. See RUBY-3602.
35
+ option :include_server_address_in_errors, default: false
36
+
32
37
  # Set the configuration options.
33
38
  #
34
39
  # @example Set the options.
@@ -287,7 +287,7 @@ module Mongo
287
287
  @internal_client ||= client.with(
288
288
  auto_encryption_options: nil,
289
289
  min_pool_size: 0,
290
- monitoring: client.send(:monitoring)
290
+ monitoring: client.monitoring
291
291
  )
292
292
  end
293
293
  end
@@ -139,14 +139,17 @@ module Mongo
139
139
  # Wraps a String with a mongocrypt_binary_t, yielding an FFI::Pointer
140
140
  # to the wrapped struct.
141
141
  def self.wrap_string(str)
142
- binary_p = Binding.mongocrypt_binary_new_from_data(
143
- FFI::MemoryPointer.from_string(str),
144
- str.bytesize
145
- )
142
+ # mongocrypt_binary_new_from_data does not copy the buffer, so the
143
+ # MemoryPointer must outlive the mongocrypt_binary_t. Hold it in a
144
+ # local so it stays referenced for the whole method frame; otherwise
145
+ # GC can free the buffer while libmongocrypt still points into it.
146
+ data_p = FFI::MemoryPointer.from_string(str)
147
+ binary_p = Binding.mongocrypt_binary_new_from_data(data_p, str.bytesize)
146
148
  begin
147
149
  yield binary_p
148
150
  ensure
149
151
  Binding.mongocrypt_binary_destroy(binary_p)
152
+ data_p # rubocop:disable Lint/Void -- keep the buffer alive past destroy
150
153
  end
151
154
  end
152
155
  end
@@ -106,7 +106,7 @@ module Mongo
106
106
  def self.parse_version(version)
107
107
  Gem::Version.new(version)
108
108
  rescue ArgumentError
109
- match = version.match(/\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?(-[A-Za-z+\d]+)?\z/)
109
+ match = version.match(/\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?(?:-[A-Za-z+\d]+)?\z/)
110
110
  raise ArgumentError.new("Malformed version number string #{version}") if match.nil?
111
111
 
112
112
  Gem::Version.new(
@@ -823,14 +823,15 @@ module Mongo
823
823
 
824
824
  # An enum labeling different libmognocrypt state machine states
825
825
  enum :mongocrypt_ctx_state, [
826
- :error, 0,
827
- :need_mongo_collinfo, 1,
828
- :need_mongo_markings, 2,
829
- :need_mongo_keys, 3,
830
- :need_kms, 4,
831
- :ready, 5,
832
- :done, 6,
833
- :need_kms_credentials, 7,
826
+ :error, 0,
827
+ :need_mongo_collinfo, 1,
828
+ :need_mongo_markings, 2,
829
+ :need_mongo_keys, 3,
830
+ :need_kms, 4,
831
+ :ready, 5,
832
+ :done, 6,
833
+ :need_kms_credentials, 7,
834
+ :need_mongo_collinfo_with_db, 8,
834
835
  ]
835
836
 
836
837
  # @!method self.mongocrypt_ctx_state(ctx)
@@ -1105,10 +1106,20 @@ module Mongo
1105
1106
  def self.check_kms_ctx_status(kms_context)
1106
1107
  return if yield
1107
1108
 
1109
+ kms_ctx_status(kms_context).raise_crypt_error(kms: true)
1110
+ end
1111
+
1112
+ # Read the status information for the given KmsContext into a Status
1113
+ # object without raising.
1114
+ #
1115
+ # @param [ Mongo::Crypt::KmsContext ] kms_context
1116
+ #
1117
+ # @return [ Mongo::Crypt::Status ] The KMS context status.
1118
+ def self.kms_ctx_status(kms_context)
1108
1119
  status = Status.new
1109
1120
 
1110
1121
  mongocrypt_kms_ctx_status(kms_context.kms_ctx_p, status.ref)
1111
- status.raise_crypt_error(kms: true)
1122
+ status
1112
1123
  end
1113
1124
 
1114
1125
  # @!method self.mongocrypt_kms_ctx_usleep(ctx)
@@ -1646,6 +1657,73 @@ module Mongo
1646
1657
  mongocrypt_setopt_use_need_kms_credentials_state(handle.ref)
1647
1658
  end
1648
1659
 
1660
+ # @!method self.mongocrypt_setopt_enable_multiple_collinfo(crypt)
1661
+ # @api private
1662
+ #
1663
+ # Enable support for multiple collection schemas. Required to support $lookup.
1664
+ #
1665
+ # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
1666
+ # @pre mongocrypt_init has not been called on crypt.
1667
+ # @return [ Boolean ] Whether the option was set successfully.
1668
+ attach_function(
1669
+ :mongocrypt_setopt_enable_multiple_collinfo,
1670
+ [ :pointer ],
1671
+ :bool
1672
+ )
1673
+
1674
+ # Enable support for multiple collection schemas. Required to support $lookup.
1675
+ #
1676
+ # @param [ Mongo::Crypt::Handle ] handle
1677
+ # @return [ Boolean ] Whether the option was set successfully.
1678
+ def self.setopt_enable_multiple_collinfo(handle)
1679
+ check_status(handle) do
1680
+ mongocrypt_setopt_enable_multiple_collinfo(handle.ref)
1681
+ end
1682
+ end
1683
+
1684
+ # @!method self.mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(crypt)
1685
+ # @api private
1686
+ #
1687
+ # Opt into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state.
1688
+ # A context enters this state when processing a bulkWrite command whose
1689
+ # target database differs from the command database ("admin").
1690
+ #
1691
+ # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
1692
+ attach_function(
1693
+ :mongocrypt_setopt_use_need_mongo_collinfo_with_db_state,
1694
+ [ :pointer ],
1695
+ :void
1696
+ )
1697
+
1698
+ # Opt into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state.
1699
+ #
1700
+ # @param [ Mongo::Crypt::Handle ] handle
1701
+ def self.setopt_use_need_mongo_collinfo_with_db_state(handle)
1702
+ mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(handle.ref)
1703
+ end
1704
+
1705
+ # @!method self.mongocrypt_ctx_mongo_db(ctx)
1706
+ # @api private
1707
+ #
1708
+ # Get the database name for the current collinfo operation. Used in the
1709
+ # MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state to determine which
1710
+ # database to run listCollections against.
1711
+ #
1712
+ # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object.
1713
+ # @return [ String, nil ] The database name, or nil on error.
1714
+ attach_function :mongocrypt_ctx_mongo_db, [ :pointer ], :string
1715
+
1716
+ # Get the database name for the current collinfo operation.
1717
+ #
1718
+ # @param [ Mongo::Crypt::Context ] context
1719
+ # @return [ String ] The database name.
1720
+ def self.ctx_mongo_db(context)
1721
+ db_name = mongocrypt_ctx_mongo_db(context.ctx_p)
1722
+ return db_name if db_name
1723
+
1724
+ check_ctx_status(context)
1725
+ end
1726
+
1649
1727
  # @!method self.mongocrypt_ctx_provide_kms_providers(ctx, kms_providers)
1650
1728
  # @api private
1651
1729
  #
@@ -76,7 +76,7 @@ module Mongo
76
76
  # This method is not currently unit tested. It is integration tested
77
77
  # in spec/integration/explicit_encryption_spec.rb
78
78
  def run_state_machine(timeout_holder)
79
- while true
79
+ loop do
80
80
  timeout_ms = timeout_holder.remaining_timeout_ms!
81
81
  case state
82
82
  when :error
@@ -90,6 +90,8 @@ module Mongo
90
90
  provide_keys(timeout_ms)
91
91
  when :need_mongo_collinfo
92
92
  provide_collection_info(timeout_ms)
93
+ when :need_mongo_collinfo_with_db
94
+ provide_collection_info_with_db(timeout_ms)
93
95
  when :need_mongo_markings
94
96
  provide_markings(timeout_ms)
95
97
  when :need_kms
@@ -122,17 +124,26 @@ module Mongo
122
124
  def provide_markings(timeout_ms)
123
125
  cmd = Binding.ctx_mongo_op(self)
124
126
 
125
- result = @encryption_io.mark_command(cmd, timeout_ms: timeout_ms)
127
+ result = @encryption_io.mark_command(cmd, db_name: @db_name, timeout_ms: timeout_ms)
126
128
  mongocrypt_feed(result)
127
129
 
128
130
  mongocrypt_done
129
131
  end
130
132
 
131
133
  def provide_collection_info(timeout_ms)
134
+ feed_collection_info(@db_name, timeout_ms)
135
+ end
136
+
137
+ def provide_collection_info_with_db(timeout_ms)
138
+ feed_collection_info(Binding.ctx_mongo_db(self), timeout_ms)
139
+ end
140
+
141
+ def feed_collection_info(db_name, timeout_ms)
132
142
  filter = Binding.ctx_mongo_op(self)
133
143
 
134
- result = @encryption_io.collection_info(@db_name, filter, timeout_ms: timeout_ms)
135
- mongocrypt_feed(result) if result
144
+ @encryption_io.collection_info(db_name, filter, timeout_ms: timeout_ms).each do |result|
145
+ mongocrypt_feed(result)
146
+ end
136
147
 
137
148
  mongocrypt_done
138
149
  end
@@ -159,12 +170,29 @@ module Mongo
159
170
  raise unless e.network_error?
160
171
  next if Binding.kms_ctx_fail(kms_context)
161
172
 
162
- raise
173
+ raise_kms_retry_error(kms_context, e)
163
174
  end
164
175
  end
165
176
  Binding.ctx_kms_done(self)
166
177
  end
167
178
 
179
+ # Raise a KmsError that wraps the KMS status message (which describes the
180
+ # retry exhaustion) with the error from the last attempt.
181
+ #
182
+ # @param [ Mongo::Crypt::KmsContext ] kms_context
183
+ # @param [ Mongo::Error::KmsError ] last_error The error from the last
184
+ # KMS request attempt.
185
+ #
186
+ # @raise [ Mongo::Error::KmsError ]
187
+ def raise_kms_retry_error(kms_context, last_error)
188
+ status = Binding.kms_ctx_status(kms_context)
189
+ raise Error::KmsError.new(
190
+ "#{status.message}, last attempt failed with: #{last_error.message}",
191
+ code: status.code,
192
+ network_error: true
193
+ )
194
+ end
195
+
168
196
  # Indicate that state machine is done feeding I/O responses back to libmongocrypt
169
197
  def mongocrypt_done
170
198
  Binding.mongocrypt_ctx_mongo_done(ctx_p)
@@ -188,21 +216,24 @@ module Mongo
188
216
  # KMS providers.
189
217
  def retrieve_kms_credentials(timeout_holder)
190
218
  providers = {}
191
- if kms_providers.aws && kms_providers.aws.empty?
192
- begin
193
- aws_credentials = Mongo::Auth::Aws::CredentialsRetriever.new.credentials(timeout_holder)
194
- rescue Auth::Aws::CredentialsNotFound
195
- raise Error::CryptError.new(
196
- 'Could not locate AWS credentials (checked environment variables, ECS and EC2 metadata)'
197
- )
219
+ kms_providers.credentials_map.each do |identifier, creds|
220
+ next unless creds.empty?
221
+
222
+ case KMS.provider_base_type(identifier)
223
+ when 'aws'
224
+ begin
225
+ aws_credentials = Mongo::Auth::Aws::CredentialsRetriever.new.credentials(timeout_holder)
226
+ rescue Auth::Aws::CredentialsNotFound
227
+ raise Error::CryptError.new(
228
+ 'Could not locate AWS credentials (checked environment variables, ECS and EC2 metadata)'
229
+ )
230
+ end
231
+ providers[identifier] = aws_credentials.to_h
232
+ when 'gcp'
233
+ providers[identifier] = { access_token: gcp_access_token(timeout_holder) }
234
+ when 'azure'
235
+ providers[identifier] = { access_token: azure_access_token(timeout_holder) }
198
236
  end
199
- providers[:aws] = aws_credentials.to_h
200
- end
201
- if kms_providers.gcp && kms_providers.gcp.empty?
202
- providers[:gcp] = { access_token: gcp_access_token(timeout_holder) }
203
- end
204
- if kms_providers.azure && kms_providers.azure.empty?
205
- providers[:azure] = { access_token: azure_access_token(timeout_holder) }
206
237
  end
207
238
  KMS::Credentials.new(providers)
208
239
  end
@@ -97,7 +97,7 @@ module Mongo
97
97
  # Must be a non-negative integer. An explicit value of 0 means infinite.
98
98
  # The default value is unset which means the feature is not enabled.
99
99
  #
100
- # @return [ Hash ] The collection information
100
+ # @return [ Array<BSON::Document> ] The collection information documents
101
101
  def collection_info(db_name, filter, timeout_ms: nil)
102
102
  unless @metadata_client
103
103
  raise ArgumentError,
@@ -108,18 +108,22 @@ module Mongo
108
108
  .use(db_name)
109
109
  .database
110
110
  .list_collections(filter: filter, deserialize_as_bson: true, timeout_ms: timeout_ms)
111
- .first
111
+ .to_a
112
112
  end
113
113
 
114
114
  # Send the command to mongocryptd to be marked with intent-to-encrypt markings
115
115
  #
116
116
  # @param [ Hash ] cmd
117
+ # @param [ String | nil ] :db_name The database against which the command
118
+ # is being run. When provided, the command is sent to mongocryptd using
119
+ # this database so that the namespace in the command matches the namespace
120
+ # in encryptionInformation.
117
121
  # @param [ Integer ] :timeout_ms The operation timeout in milliseconds.
118
122
  # Must be a non-negative integer. An explicit value of 0 means infinite.
119
123
  # The default value is unset which means the feature is not enabled.
120
124
  #
121
125
  # @return [ Hash ] The marked command
122
- def mark_command(cmd, timeout_ms: nil)
126
+ def mark_command(cmd, db_name: nil, timeout_ms: nil)
123
127
  unless @mongocryptd_client
124
128
  raise ArgumentError,
125
129
  'mark_command requires mongocryptd_client to have been passed to the constructor, but it was not'
@@ -132,13 +136,15 @@ module Mongo
132
136
  timeout_ms: timeout_ms
133
137
  }
134
138
 
139
+ mongocryptd = db_name ? @mongocryptd_client.use(db_name) : @mongocryptd_client
140
+
135
141
  begin
136
- response = @mongocryptd_client.database.command(cmd, options)
142
+ response = mongocryptd.database.command(cmd, options)
137
143
  rescue Error::NoServerAvailable => e
138
144
  raise e if @options[:mongocryptd_bypass_spawn]
139
145
 
140
146
  spawn_mongocryptd
141
- response = @mongocryptd_client.database.command(cmd, options)
147
+ response = mongocryptd.database.command(cmd, options)
142
148
  end
143
149
 
144
150
  response.first
@@ -74,6 +74,8 @@ module Mongo
74
74
  Binding.method(:mongocrypt_destroy)
75
75
  )
76
76
  Binding.kms_ctx_setopt_retry_kms(self, true)
77
+ Binding.setopt_enable_multiple_collinfo(self)
78
+ Binding.setopt_use_need_mongo_collinfo_with_db_state(self)
77
79
  @kms_providers = kms_providers
78
80
  @kms_tls_options = kms_tls_options
79
81
 
@@ -101,7 +103,7 @@ module Mongo
101
103
 
102
104
  Binding.setopt_kms_providers(self, @kms_providers.to_document)
103
105
 
104
- if @kms_providers.aws&.empty? || @kms_providers.gcp&.empty? || @kms_providers.azure&.empty?
106
+ if @kms_providers.any_on_demand?
105
107
  Binding.setopt_use_need_kms_credentials_state(self)
106
108
  end
107
109
 
@@ -123,13 +125,23 @@ module Mongo
123
125
  end
124
126
 
125
127
  # Return TLS options for KMS provider. If there are no TLS options set,
126
- # empty hash is returned.
128
+ # empty hash is returned. Named providers (e.g. "kmip:name1") fall back
129
+ # to the base-type options (e.g. :kmip) when no exact match is found.
127
130
  #
128
- # @param [ String ] provider KSM provider name.
131
+ # @param [ String ] provider KMS provider name or named identifier.
129
132
  #
130
133
  # @return [ Hash ] TLS options to connect to KMS provider.
131
134
  def kms_tls_options(provider)
132
- @kms_tls_options.fetch(provider, {})
135
+ provider_str = provider.to_s
136
+ base_type = KMS.provider_base_type(provider_str)
137
+
138
+ @kms_tls_options.fetch(provider_str) do
139
+ @kms_tls_options.fetch(provider_str.to_sym) do
140
+ @kms_tls_options.fetch(base_type) do
141
+ @kms_tls_options.fetch(base_type.to_sym, {})
142
+ end
143
+ end
144
+ end
133
145
  end
134
146
 
135
147
  def crypt_shared_lib_version
@@ -191,7 +203,7 @@ module Mongo
191
203
  # Send the logs from libmongocrypt to the Mongo::Logger
192
204
  def set_logger_callback
193
205
  @log_callback = proc do |level, msg|
194
- @logger.send(level, msg)
206
+ @logger.public_send(level, msg)
195
207
  end
196
208
 
197
209
  Binding.setopt_log_handler(@mongocrypt, @log_callback)