mongo 2.19.2 → 2.20.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 (104) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Rakefile +27 -154
  4. data/lib/mongo/cluster/topology/base.rb +16 -0
  5. data/lib/mongo/cluster.rb +27 -1
  6. data/lib/mongo/collection/view/iterable.rb +1 -0
  7. data/lib/mongo/collection.rb +27 -3
  8. data/lib/mongo/error/transactions_not_supported.rb +34 -0
  9. data/lib/mongo/error.rb +1 -0
  10. data/lib/mongo/grid/fs_bucket.rb +6 -0
  11. data/lib/mongo/monitoring/event/secure.rb +1 -1
  12. data/lib/mongo/operation/create_search_indexes/op_msg.rb +31 -0
  13. data/lib/mongo/operation/create_search_indexes.rb +15 -0
  14. data/lib/mongo/operation/drop_search_index/op_msg.rb +33 -0
  15. data/lib/mongo/operation/drop_search_index.rb +15 -0
  16. data/lib/mongo/operation/shared/executable.rb +43 -27
  17. data/lib/mongo/operation/shared/response_handling.rb +23 -25
  18. data/lib/mongo/operation/shared/specifiable.rb +7 -0
  19. data/lib/mongo/operation/update_search_index/op_msg.rb +34 -0
  20. data/lib/mongo/operation/update_search_index.rb +15 -0
  21. data/lib/mongo/operation.rb +3 -0
  22. data/lib/mongo/retryable/read_worker.rb +7 -6
  23. data/lib/mongo/retryable/write_worker.rb +7 -4
  24. data/lib/mongo/retryable.rb +2 -2
  25. data/lib/mongo/search_index/view.rb +232 -0
  26. data/lib/mongo/server/app_metadata/environment.rb +64 -9
  27. data/lib/mongo/server/app_metadata.rb +5 -4
  28. data/lib/mongo/server/description/features.rb +1 -0
  29. data/lib/mongo/server_selector/base.rb +32 -6
  30. data/lib/mongo/session/server_session/dirtyable.rb +52 -0
  31. data/lib/mongo/session/server_session.rb +3 -0
  32. data/lib/mongo/session/session_pool.rb +12 -18
  33. data/lib/mongo/session.rb +32 -0
  34. data/lib/mongo/uri.rb +0 -4
  35. data/lib/mongo/version.rb +1 -1
  36. data/lib/mongo.rb +1 -0
  37. data/mongo.gemspec +1 -7
  38. data/spec/atlas/atlas_connectivity_spec.rb +5 -9
  39. data/spec/atlas/operations_spec.rb +1 -5
  40. data/spec/faas/ruby-sam-app/Gemfile +9 -0
  41. data/spec/faas/ruby-sam-app/mongodb/Gemfile +4 -0
  42. data/spec/faas/ruby-sam-app/mongodb/app.rb +149 -0
  43. data/spec/faas/ruby-sam-app/template.yaml +48 -0
  44. data/spec/integration/client_side_encryption/corpus_spec.rb +10 -2
  45. data/spec/integration/retryable_reads_errors_spec.rb +161 -8
  46. data/spec/integration/retryable_writes_errors_spec.rb +156 -0
  47. data/spec/integration/search_indexes_prose_spec.rb +168 -0
  48. data/spec/lite_spec_helper.rb +32 -10
  49. data/spec/mongo/cluster_spec.rb +36 -0
  50. data/spec/mongo/collection/view/aggregation_spec.rb +6 -1
  51. data/spec/mongo/collection/view/explainable_spec.rb +2 -0
  52. data/spec/mongo/collection_crud_spec.rb +1 -1
  53. data/spec/mongo/operation/insert_spec.rb +1 -1
  54. data/spec/mongo/retryable/write_worker_spec.rb +39 -0
  55. data/spec/mongo/server/app_metadata/environment_spec.rb +135 -0
  56. data/spec/mongo/server/app_metadata_spec.rb +12 -2
  57. data/spec/mongo/server/connection_spec.rb +4 -0
  58. data/spec/mongo/session/session_pool_spec.rb +1 -16
  59. data/spec/mongo/session_transaction_spec.rb +15 -0
  60. data/spec/mongo/uri_spec.rb +0 -9
  61. data/spec/runners/crud/test.rb +0 -8
  62. data/spec/runners/crud.rb +1 -1
  63. data/spec/runners/transactions/test.rb +12 -3
  64. data/spec/runners/unified/assertions.rb +16 -3
  65. data/spec/runners/unified/crud_operations.rb +12 -0
  66. data/spec/runners/unified/search_index_operations.rb +63 -0
  67. data/spec/runners/unified/support_operations.rb +3 -5
  68. data/spec/runners/unified/test.rb +11 -2
  69. data/spec/shared/lib/mrss/docker_runner.rb +3 -0
  70. data/spec/shared/share/Dockerfile.erb +20 -69
  71. data/spec/shared/shlib/server.sh +1 -0
  72. data/spec/shared/shlib/set_env.sh +5 -28
  73. data/spec/spec_helper.rb +1 -1
  74. data/spec/spec_tests/data/client_side_encryption/explain.yml +2 -2
  75. data/spec/spec_tests/data/connection_string/invalid-uris.yml +0 -10
  76. data/spec/spec_tests/data/connection_string/valid-options.yml +13 -0
  77. data/spec/spec_tests/data/crud_unified/find-test-all-options.yml +348 -0
  78. data/spec/spec_tests/data/index_management/createSearchIndex.yml +64 -0
  79. data/spec/spec_tests/data/index_management/createSearchIndexes.yml +86 -0
  80. data/spec/spec_tests/data/index_management/dropSearchIndex.yml +43 -0
  81. data/spec/spec_tests/data/index_management/listSearchIndexes.yml +91 -0
  82. data/spec/spec_tests/data/index_management/updateSearchIndex.yml +46 -0
  83. data/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml +3 -6
  84. data/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml +3 -6
  85. data/spec/spec_tests/data/run_command_unified/runCommand.yml +319 -0
  86. data/spec/spec_tests/data/sessions_unified/driver-sessions-dirty-session-errors.yml +351 -0
  87. data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +1 -1
  88. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +7 -7
  89. data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +3 -4
  90. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +1 -1
  91. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +1 -1
  92. data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +3 -3
  93. data/spec/spec_tests/index_management_unified_spec.rb +13 -0
  94. data/spec/spec_tests/run_command_unified_spec.rb +13 -0
  95. data/spec/spec_tests/sdam_unified_spec.rb +2 -0
  96. data/spec/support/constraints.rb +6 -0
  97. data/spec/support/faas/app/aws_lambda/mongodb/Gemfile.lock +19 -0
  98. data/spec/support/ocsp +1 -1
  99. data/spec/support/recording_logger.rb +27 -0
  100. data/spec/support/spec_config.rb +5 -0
  101. data.tar.gz.sig +0 -0
  102. metadata +1329 -1285
  103. metadata.gz.sig +3 -2
  104. data/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml +0 -49
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2024 MongoDB Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Mongo
18
+ class Session
19
+ class ServerSession
20
+ # Functionality for manipulating and querying a session's
21
+ # "dirty" state, per the last paragraph at
22
+ # https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#server-session-pool
23
+ #
24
+ # If a driver has a server session pool and a network error is
25
+ # encountered when executing any command with a ClientSession, the
26
+ # driver MUST mark the associated ServerSession as dirty. Dirty server
27
+ # sessions are discarded when returned to the server session pool. It is
28
+ # valid for a dirty session to be used for subsequent commands (e.g. an
29
+ # implicit retry attempt, a later command in a bulk write, or a later
30
+ # operation on an explicit session), however, it MUST remain dirty for
31
+ # the remainder of its lifetime regardless if later commands succeed.
32
+ #
33
+ # @api private
34
+ module Dirtyable
35
+ # Query whether the server session has been marked dirty or not.
36
+ #
37
+ # @return [ true | false ] the server session's dirty state
38
+ def dirty?
39
+ @dirty
40
+ end
41
+
42
+ # Mark the server session as dirty (the default) or clean.
43
+ #
44
+ # @param [ true | false ] mark whether the mark the server session
45
+ # dirty or not.
46
+ def dirty!(mark = true)
47
+ @dirty = mark
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -15,6 +15,8 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
+ require 'mongo/session/server_session/dirtyable'
19
+
18
20
  module Mongo
19
21
 
20
22
  class Session
@@ -25,6 +27,7 @@ module Mongo
25
27
  #
26
28
  # @since 2.5.0
27
29
  class ServerSession
30
+ include Dirtyable
28
31
 
29
32
  # Regex for removing dashes from the UUID string.
30
33
  #
@@ -25,21 +25,6 @@ module Mongo
25
25
  #
26
26
  # @since 2.5.0
27
27
  class SessionPool
28
-
29
- # Create a SessionPool.
30
- #
31
- # @example
32
- # SessionPool.create(cluster)
33
- #
34
- # @param [ Mongo::Cluster ] cluster The cluster that will be associated with this
35
- # session pool.
36
- #
37
- # @since 2.5.0
38
- def self.create(cluster)
39
- pool = new(cluster)
40
- cluster.instance_variable_set(:@session_pool, pool)
41
- end
42
-
43
28
  # Initialize a SessionPool.
44
29
  #
45
30
  # @example
@@ -105,9 +90,7 @@ module Mongo
105
90
 
106
91
  @mutex.synchronize do
107
92
  prune!
108
- unless about_to_expire?(session)
109
- @queue.unshift(session)
110
- end
93
+ @queue.unshift(session) if return_to_queue?(session)
111
94
  end
112
95
  end
113
96
 
@@ -136,6 +119,17 @@ module Mongo
136
119
 
137
120
  private
138
121
 
122
+ # Query whether the given session is okay to return to the
123
+ # pool's queue.
124
+ #
125
+ # @param [ Session::ServerSession ] session the session to query
126
+ #
127
+ # @return [ true | false ] whether to return the session to the
128
+ # queue.
129
+ def return_to_queue?(session)
130
+ !session.dirty? && !about_to_expire?(session)
131
+ end
132
+
139
133
  def about_to_expire?(session)
140
134
  if session.nil?
141
135
  raise ArgumentError, 'session cannot be nil'
data/lib/mongo/session.rb CHANGED
@@ -123,6 +123,23 @@ module Mongo
123
123
  # @since 2.5.0
124
124
  attr_reader :operation_time
125
125
 
126
+ # Sets the dirty state to the given value for the underlying server
127
+ # session. If there is no server session, this does nothing.
128
+ #
129
+ # @param [ true | false ] mark whether to mark the server session as
130
+ # dirty, or not.
131
+ def dirty!(mark = true)
132
+ @server_session&.dirty!(mark)
133
+ end
134
+
135
+ # @return [ true | false | nil ] whether the underlying server session is
136
+ # dirty. If no server session exists for this session, returns nil.
137
+ #
138
+ # @api private
139
+ def dirty?
140
+ @server_session&.dirty?
141
+ end
142
+
126
143
  # @return [ Hash ] The options for the transaction currently being executed
127
144
  # on this session.
128
145
  #
@@ -538,6 +555,8 @@ module Mongo
538
555
  #
539
556
  # @since 2.6.0
540
557
  def start_transaction(options = nil)
558
+ check_transactions_supported!
559
+
541
560
  if options
542
561
  Lint.validate_read_concern_option(options[:read_concern])
543
562
 
@@ -1185,5 +1204,18 @@ module Mongo
1185
1204
  raise Mongo::Error::InvalidSession.new(MISMATCHED_CLUSTER_ERROR_MSG)
1186
1205
  end
1187
1206
  end
1207
+
1208
+ def check_transactions_supported!
1209
+ raise Mongo::Error::TransactionsNotSupported, "standalone topology" if cluster.single?
1210
+
1211
+ cluster.next_primary.with_connection do |conn|
1212
+ if cluster.replica_set? && !conn.features.transactions_enabled?
1213
+ raise Mongo::Error::TransactionsNotSupported, "server version is < 4.0"
1214
+ end
1215
+ if cluster.sharded? && !conn.features.sharded_transactions_enabled?
1216
+ raise Mongo::Error::TransactionsNotSupported, "sharded transactions require server version >= 4.2"
1217
+ end
1218
+ end
1219
+ end
1188
1220
  end
1189
1221
  end
data/lib/mongo/uri.rb CHANGED
@@ -377,10 +377,6 @@ module Mongo
377
377
  raise_invalid_error!("Options contain an unescaped question mark (?), or the database name contains a question mark and was not escaped")
378
378
  end
379
379
 
380
- if options && !hosts_and_db.index('/')
381
- raise_invalid_error!("MongoDB URI must have a slash (/) after the hosts if options are given")
382
- end
383
-
384
380
  hosts, db = hosts_and_db.split('/', 2)
385
381
  if db && db.index('/')
386
382
  raise_invalid_error!("Database name contains an unescaped slash (/): #{db}")
data/lib/mongo/version.rb CHANGED
@@ -20,5 +20,5 @@ module Mongo
20
20
  # The current version of the driver.
21
21
  #
22
22
  # @since 2.0.0
23
- VERSION = '2.19.2'.freeze
23
+ VERSION = '2.20.0'.freeze
24
24
  end
data/lib/mongo.rb CHANGED
@@ -64,6 +64,7 @@ require 'mongo/client_encryption'
64
64
  require 'mongo/dbref'
65
65
  require 'mongo/grid'
66
66
  require 'mongo/index'
67
+ require 'mongo/search_index/view'
67
68
  require 'mongo/lint'
68
69
  require 'mongo/query_cache'
69
70
  require 'mongo/server'
data/mongo.gemspec CHANGED
@@ -41,11 +41,5 @@ Gem::Specification.new do |s|
41
41
 
42
42
  s.required_ruby_version = ">= 2.5"
43
43
 
44
- # For testing driver against bson master we need to depend on bson < 6.0.0
45
- # but in release version we want to depend on bson < 5.0.0.
46
- if %w(1 yes true).include?(ENV['MONGO_RUBY_DRIVER_BSON_MASTER'])
47
- s.add_dependency 'bson', '>=4.13.0', '<6.0.0'
48
- else
49
- s.add_dependency 'bson', '>=4.14.1', '<5.0.0'
50
- end
44
+ s.add_dependency 'bson', '>=4.14.1', '<6.0.0'
51
45
  end
@@ -7,21 +7,17 @@ describe 'Atlas connectivity' do
7
7
  let(:uri) { ENV['ATLAS_URI'] }
8
8
  let(:client) { Mongo::Client.new(uri) }
9
9
 
10
- before do
11
- if uri.nil?
12
- skip "ATLAS_URI not set in environment"
13
- end
14
- end
10
+ require_atlas
15
11
 
16
12
  describe 'connection to Atlas' do
17
13
  it 'runs ismaster successfully' do
18
- result = client.database.command(:ismaster => 1)
19
- expect(result.documents.first['ismaster']).to be true
14
+ expect { client.database.command(:hello => 1) }
15
+ .not_to raise_error
20
16
  end
21
17
 
22
18
  it 'runs findOne successfully' do
23
- result = client.use(:test)['test'].find.to_a
24
- expect(result).to be_a(Array)
19
+ expect { client.use(:test)['test'].find.to_a }
20
+ .not_to raise_error
25
21
  end
26
22
  end
27
23
  end
@@ -7,11 +7,7 @@ describe 'Operations' do
7
7
  let(:uri) { ENV['ATLAS_URI'] }
8
8
  let(:client) { Mongo::Client.new(uri) }
9
9
 
10
- before do
11
- if uri.nil?
12
- skip "ATLAS_URI not set in environment"
13
- end
14
- end
10
+ require_atlas
15
11
 
16
12
  describe 'ping' do
17
13
  it 'works' do
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "httparty"
4
+ gem "mongo"
5
+
6
+ group :test do
7
+ gem "test-unit"
8
+ gem "mocha"
9
+ end
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "httparty"
4
+ gem "mongo"
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongo'
4
+ require 'json'
5
+
6
+ class StatsAggregator
7
+
8
+ def initialize
9
+ @open_connections = 0
10
+ @heartbeats_count = 0
11
+ @total_heartbeat_time = 0
12
+ @commands_count = 0
13
+ @total_command_time = 0
14
+ end
15
+
16
+ def add_command(duration)
17
+ @commands_count += 1
18
+ @total_command_time += duration
19
+ end
20
+
21
+ def add_heartbeat(duration)
22
+ @heartbeats_count += 1
23
+ @total_heartbeat_time += duration
24
+ end
25
+
26
+ def add_connection
27
+ @open_connections += 1
28
+ end
29
+
30
+ def remove_connection
31
+ @open_connections -= 1
32
+ end
33
+
34
+ def average_heartbeat_time
35
+ if @heartbeats_count == 0
36
+ 0
37
+ else
38
+ @total_heartbeat_time / @heartbeats_count
39
+ end
40
+ end
41
+
42
+ def average_command_time
43
+ if @commands_count == 0
44
+ 0
45
+ else
46
+ @total_command_time / @commands_count
47
+ end
48
+ end
49
+
50
+ def reset
51
+ @open_connections = 0
52
+ @heartbeats_count = 0
53
+ @total_heartbeat_time = 0
54
+ @commands_count = 0
55
+ @total_command_time = 0
56
+ end
57
+
58
+ def result
59
+ {
60
+ average_heartbeat_time: average_heartbeat_time,
61
+ average_command_time: average_command_time,
62
+ heartbeats_count: @heartbeats_count,
63
+ open_connections: @open_connections,
64
+ }
65
+ end
66
+ end
67
+
68
+ class CommandMonitor
69
+
70
+ def initialize(stats_aggregator)
71
+ @stats_aggregator = stats_aggregator
72
+ end
73
+
74
+ def started(event); end
75
+
76
+ def failed(event)
77
+ @stats_aggregator.add_command(event.duration)
78
+ end
79
+
80
+ def succeeded(event)
81
+ @stats_aggregator.add_command(event.duration)
82
+ end
83
+ end
84
+
85
+ class HeartbeatMonitor
86
+
87
+ def initialize(stats_aggregator)
88
+ @stats_aggregator = stats_aggregator
89
+ end
90
+
91
+ def started(event); end
92
+
93
+ def succeeded(event)
94
+ @stats_aggregator.add_heartbeat(event.duration)
95
+ end
96
+
97
+ def failed(event)
98
+ @stats_aggregator.add_heartbeat(event.duration)
99
+ end
100
+ end
101
+
102
+ class PoolMonitor
103
+
104
+ def initialize(stats_aggregator)
105
+ @stats_aggregator = stats_aggregator
106
+ end
107
+
108
+ def published(event)
109
+ case event
110
+ when Mongo::Monitoring::Event::Cmap::ConnectionCreated
111
+ @stats_aggregator.add_connection
112
+ when Mongo::Monitoring::Event::Cmap::ConnectionClosed
113
+ @stats_aggregator.remove_connection
114
+ end
115
+ end
116
+ end
117
+
118
+ $stats_aggregator = StatsAggregator.new
119
+
120
+ command_monitor = CommandMonitor.new($stats_aggregator)
121
+ heartbeat_monitor = HeartbeatMonitor.new($stats_aggregator)
122
+ pool_monitor = PoolMonitor.new($stats_aggregator)
123
+
124
+ sdam_proc = proc do |client|
125
+ client.subscribe(Mongo::Monitoring::COMMAND, command_monitor)
126
+ client.subscribe(Mongo::Monitoring::SERVER_HEARTBEAT, heartbeat_monitor)
127
+ client.subscribe(Mongo::Monitoring::CONNECTION_POOL, pool_monitor)
128
+ end
129
+
130
+ puts 'Connecting'
131
+ $client = Mongo::Client.new(ENV['MONGODB_URI'], sdam_proc: sdam_proc)
132
+ # Populate the connection pool
133
+ $client.use('lambda_test').database.list_collections
134
+ puts 'Connected'
135
+
136
+ def lambda_handler(event:, context:)
137
+ db = $client.use('lambda_test')
138
+ collection = db[:test_collection]
139
+ result = collection.insert_one({ name: 'test' })
140
+ collection.delete_one({ _id: result.inserted_id })
141
+ response = $stats_aggregator.result.to_json
142
+ $stats_aggregator.reset
143
+ puts "Response: #{response}"
144
+
145
+ {
146
+ statusCode: 200,
147
+ body: response
148
+ }
149
+ end
@@ -0,0 +1,48 @@
1
+ AWSTemplateFormatVersion: '2010-09-09'
2
+ Transform: AWS::Serverless-2016-10-31
3
+ Description: >
4
+ Sample SAM Template for ruby-sam-app
5
+
6
+ # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
7
+ Globals:
8
+ Function:
9
+ Timeout: 30
10
+ MemorySize: 128
11
+
12
+ Parameters:
13
+ MongoDbUri:
14
+ Type: String
15
+ Description: The MongoDB connection string.
16
+
17
+ Resources:
18
+ MongoDBFunction:
19
+ Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
20
+ Properties:
21
+ CodeUri: mongodb/
22
+ Environment:
23
+ Variables:
24
+ MONGODB_URI: !Ref MongoDbUri
25
+ Handler: app.lambda_handler
26
+ Runtime: ruby3.2
27
+ Architectures:
28
+ - x86_64
29
+ Events:
30
+ MongoDB:
31
+ Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
32
+ Properties:
33
+ Path: /mongodb
34
+ Method: get
35
+
36
+ Outputs:
37
+ # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
38
+ # Find out more about other implicit resources you can reference within SAM
39
+ # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
40
+ MongoDBApi:
41
+ Description: "API Gateway endpoint URL for Prod stage for MongoDB function"
42
+ Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/mongodb/"
43
+ MongoDBFunction:
44
+ Description: "MongoDB Lambda Function ARN"
45
+ Value: !GetAtt MongoDBFunction.Arn
46
+ MongoDBFunctionIamRole:
47
+ Description: "Implicit IAM Role created for MongoDB function"
48
+ Value: !GetAtt MongoDBFunctionRole.Arn
@@ -188,6 +188,15 @@ describe 'Client-Side Encryption' do
188
188
  key_vault_collection.insert_one(kmip_data_key)
189
189
  end
190
190
 
191
+ # This method compensates for an API change between BSON 4 and
192
+ # BSON 5.
193
+ def normalize_cse_value(a)
194
+ case a
195
+ when BSON::Decimal128 then a.to_d
196
+ else a
197
+ end
198
+ end
199
+
191
200
  shared_context 'with jsonSchema collection validator' do
192
201
  let(:local_schema_map) { nil }
193
202
 
@@ -228,12 +237,11 @@ describe 'Client-Side Encryption' do
228
237
  .find(_id: corpus_encrypted_id)
229
238
  .first
230
239
 
231
-
232
240
  corpus_encrypted_actual.each do |key, value|
233
241
  # If it was deterministically encrypted, test the encrypted values
234
242
  # for equality.
235
243
  if value['algo'] == 'det'
236
- expect(value['value']).to eq(corpus_encrypted_expected[key]['value'])
244
+ expect(normalize_cse_value(value['value'])).to eq(normalize_cse_value(corpus_encrypted_expected[key]['value']))
237
245
  else
238
246
  # If the document was randomly encrypted, the two encrypted values
239
247
  # will not be equal. Ensure that they are equal when decrypted.
@@ -20,14 +20,14 @@ describe 'Retryable reads errors tests' do
20
20
 
21
21
  let(:failpoint) do
22
22
  {
23
- configureFailPoint: "failCommand",
24
- mode: { times: 1 },
25
- data: {
26
- failCommands: [ "find" ],
27
- errorCode: 91,
28
- blockConnection: true,
29
- blockTimeMS: 1000
30
- }
23
+ configureFailPoint: "failCommand",
24
+ mode: { times: 1 },
25
+ data: {
26
+ failCommands: [ "find" ],
27
+ errorCode: 91,
28
+ blockConnection: true,
29
+ blockTimeMS: 1000
30
+ }
31
31
  }
32
32
  end
33
33
 
@@ -107,4 +107,157 @@ describe 'Retryable reads errors tests' do
107
107
  })
108
108
  end
109
109
  end
110
+
111
+ context 'Retries in a sharded cluster' do
112
+ require_topology :sharded
113
+ min_server_version '4.2'
114
+ require_no_auth
115
+
116
+ let(:subscriber) { Mrss::EventSubscriber.new }
117
+
118
+ let(:find_started_events) do
119
+ subscriber.started_events.select { |e| e.command_name == "find" }
120
+ end
121
+
122
+ let(:find_failed_events) do
123
+ subscriber.failed_events.select { |e| e.command_name == "find" }
124
+ end
125
+
126
+ let(:find_succeeded_events) do
127
+ subscriber.succeeded_events.select { |e| e.command_name == "find" }
128
+ end
129
+
130
+ context 'when another mongos is available' do
131
+
132
+ let(:first_mongos) do
133
+ Mongo::Client.new(
134
+ [SpecConfig.instance.addresses.first],
135
+ direct_connection: true,
136
+ database: 'admin'
137
+ )
138
+ end
139
+
140
+ let(:second_mongos) do
141
+ Mongo::Client.new(
142
+ [SpecConfig.instance.addresses.last],
143
+ direct_connection: false,
144
+ database: 'admin'
145
+ )
146
+ end
147
+
148
+ let(:client) do
149
+ new_local_client(
150
+ [
151
+ SpecConfig.instance.addresses.first,
152
+ SpecConfig.instance.addresses.last,
153
+ ],
154
+ SpecConfig.instance.test_options.merge(retry_reads: true)
155
+ )
156
+ end
157
+
158
+ let(:expected_servers) do
159
+ [
160
+ SpecConfig.instance.addresses.first.to_s,
161
+ SpecConfig.instance.addresses.last.to_s
162
+ ].sort
163
+ end
164
+
165
+ before do
166
+ skip 'This test requires at least two mongos' if SpecConfig.instance.addresses.length < 2
167
+
168
+ first_mongos.database.command(
169
+ configureFailPoint: 'failCommand',
170
+ mode: { times: 1 },
171
+ data: {
172
+ failCommands: %w(find),
173
+ closeConnection: false,
174
+ errorCode: 6
175
+ }
176
+ )
177
+
178
+ second_mongos.database.command(
179
+ configureFailPoint: 'failCommand',
180
+ mode: { times: 1 },
181
+ data: {
182
+ failCommands: %w(find),
183
+ closeConnection: false,
184
+ errorCode: 6
185
+ }
186
+ )
187
+ end
188
+
189
+ after do
190
+ [first_mongos, second_mongos].each do |admin_client|
191
+ admin_client.database.command(
192
+ configureFailPoint: 'failCommand',
193
+ mode: 'off'
194
+ )
195
+ admin_client.close
196
+ end
197
+ client.close
198
+ end
199
+
200
+ it 'retries on different mongos' do
201
+ client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
202
+ expect { collection.find.first }.to raise_error(Mongo::Error::OperationFailure)
203
+ expect(find_started_events.map { |e| e.address.to_s }.sort).to eq(expected_servers)
204
+ expect(find_failed_events.map { |e| e.address.to_s }.sort).to eq(expected_servers)
205
+ end
206
+ end
207
+
208
+ context 'when no other mongos is available' do
209
+ let(:mongos) do
210
+ Mongo::Client.new(
211
+ [SpecConfig.instance.addresses.first],
212
+ direct_connection: true,
213
+ database: 'admin'
214
+ )
215
+ end
216
+
217
+ let(:client) do
218
+ new_local_client(
219
+ [
220
+ SpecConfig.instance.addresses.first
221
+ ],
222
+ SpecConfig.instance.test_options.merge(retry_reads: true)
223
+ )
224
+ end
225
+
226
+ before do
227
+ mongos.database.command(
228
+ configureFailPoint: 'failCommand',
229
+ mode: { times: 1 },
230
+ data: {
231
+ failCommands: %w(find),
232
+ closeConnection: false,
233
+ errorCode: 6
234
+ }
235
+ )
236
+ end
237
+
238
+ after do
239
+ mongos.database.command(
240
+ configureFailPoint: 'failCommand',
241
+ mode: 'off'
242
+ )
243
+ mongos.close
244
+ client.close
245
+ end
246
+
247
+ it 'retries on the same mongos' do
248
+ client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
249
+ expect { collection.find.first }.not_to raise_error
250
+ expect(find_started_events.map { |e| e.address.to_s }.sort).to eq([
251
+ SpecConfig.instance.addresses.first.to_s,
252
+ SpecConfig.instance.addresses.first.to_s
253
+ ])
254
+ expect(find_failed_events.map { |e| e.address.to_s }.sort).to eq([
255
+ SpecConfig.instance.addresses.first.to_s
256
+ ])
257
+ expect(find_succeeded_events.map { |e| e.address.to_s }.sort).to eq([
258
+ SpecConfig.instance.addresses.first.to_s
259
+ ])
260
+ end
261
+ end
262
+ end
110
263
  end