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.
- checksums.yaml +4 -4
- data/lib/mongo/bulk_write/combineable.rb +0 -7
- data/lib/mongo/bulk_write/validatable.rb +0 -4
- data/lib/mongo/bulk_write.rb +0 -34
- data/lib/mongo/client.rb +35 -5
- data/lib/mongo/client_encryption.rb +36 -11
- data/lib/mongo/cluster.rb +1 -2
- data/lib/mongo/collection/queryable_encryption.rb +7 -6
- data/lib/mongo/collection/view/aggregation.rb +9 -9
- data/lib/mongo/collection/view/change_stream.rb +1 -5
- data/lib/mongo/collection/view/iterable.rb +2 -9
- data/lib/mongo/collection/view/map_reduce.rb +5 -4
- data/lib/mongo/collection/view/readable.rb +3 -3
- data/lib/mongo/collection/view/writable.rb +0 -35
- data/lib/mongo/crypt/auto_encrypter.rb +5 -1
- data/lib/mongo/crypt/binding.rb +66 -1
- data/lib/mongo/crypt/explicit_encrypter.rb +27 -12
- data/lib/mongo/crypt/explicit_encryption_context.rb +51 -12
- data/lib/mongo/crypt/handle.rb +18 -0
- data/lib/mongo/cursor.rb +13 -0
- data/lib/mongo/database/view.rb +7 -6
- data/lib/mongo/database.rb +7 -10
- data/lib/mongo/error/unsupported_option.rb +0 -40
- data/lib/mongo/grid/fs_bucket.rb +2 -2
- data/lib/mongo/grid/stream/read.rb +2 -2
- data/lib/mongo/grid/stream/write.rb +1 -1
- data/lib/mongo/index/view.rb +3 -2
- data/lib/mongo/monitoring/event/secure.rb +6 -1
- data/lib/mongo/operation/map_reduce/result.rb +6 -2
- data/lib/mongo/operation/result.rb +9 -0
- data/lib/mongo/operation/shared/executable.rb +1 -6
- data/lib/mongo/operation/shared/result/aggregatable.rb +3 -3
- data/lib/mongo/protocol/message.rb +0 -4
- data/lib/mongo/protocol/msg.rb +1 -12
- data/lib/mongo/protocol/serializers.rb +1 -9
- data/lib/mongo/retryable/backpressure.rb +17 -2
- data/lib/mongo/retryable/read_worker.rb +1 -1
- data/lib/mongo/retryable/retry_policy.rb +2 -2
- data/lib/mongo/retryable/write_worker.rb +2 -2
- data/lib/mongo/retryable.rb +1 -1
- data/lib/mongo/server/app_metadata.rb +1 -2
- data/lib/mongo/server/connection.rb +7 -0
- data/lib/mongo/server/connection_pool.rb +60 -9
- data/lib/mongo/server/description/features.rb +2 -3
- data/lib/mongo/server/description.rb +0 -1
- data/lib/mongo/server/pending_connection.rb +0 -5
- data/lib/mongo/server.rb +1 -2
- data/lib/mongo/session.rb +10 -3
- data/lib/mongo/tracing/open_telemetry/command_tracer.rb +141 -102
- data/lib/mongo/tracing/open_telemetry/operation_tracer.rb +52 -17
- data/lib/mongo/tracing/open_telemetry/tracer.rb +10 -1
- data/lib/mongo/uri/options_mapper.rb +15 -4
- data/lib/mongo/version.rb +1 -1
- metadata +2 -2
|
@@ -37,12 +37,19 @@ module Mongo
|
|
|
37
37
|
# to TLS connection options of Mongo::Client.
|
|
38
38
|
# @param [ Integer | nil ] timeout_ms Timeout for every operation executed
|
|
39
39
|
# on this object.
|
|
40
|
-
|
|
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"
|
|
99
|
-
# provided, it defaults to a value of 0. Contention
|
|
100
|
-
# only if encryption algorithm is set to "Indexed"
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
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
|
-
#
|
|
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"
|
|
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 "
|
|
46
|
-
# Allowed values are "equality"
|
|
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 "
|
|
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 "
|
|
140
|
+
raise ArgumentError.new(':query_type is allowed only for "Indexed", "Range", or "String" algorithms')
|
|
126
141
|
end
|
|
127
142
|
end
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
data/lib/mongo/crypt/handle.rb
CHANGED
|
@@ -66,6 +66,10 @@ module Mongo
|
|
|
66
66
|
# error that libmongocrypt raises on a subsequent "$SYSTEM" search.
|
|
67
67
|
# @option options [ Logger ] :logger A Logger object to which libmongocrypt logs
|
|
68
68
|
# will be sent
|
|
69
|
+
# @option options [ Integer | nil ] :key_expiration_ms The lifetime of the
|
|
70
|
+
# data encryption key cache, in milliseconds. A value of 0 means the
|
|
71
|
+
# cache never expires. When nil, libmongocrypt's default of 60000 is
|
|
72
|
+
# used.
|
|
69
73
|
def initialize(kms_providers, kms_tls_options, options = {})
|
|
70
74
|
# FFI::AutoPointer uses a custom release strategy to automatically free
|
|
71
75
|
# the pointer once this object goes out of scope
|
|
@@ -87,6 +91,9 @@ module Mongo
|
|
|
87
91
|
@bypass_query_analysis = options[:bypass_query_analysis]
|
|
88
92
|
set_bypass_query_analysis if @bypass_query_analysis
|
|
89
93
|
|
|
94
|
+
@key_expiration_ms = options[:key_expiration_ms]
|
|
95
|
+
set_key_expiration unless @key_expiration_ms.nil?
|
|
96
|
+
|
|
90
97
|
@crypt_shared_lib_path = options[:crypt_shared_lib_path]
|
|
91
98
|
@explicit_encryption_only = options[:explicit_encryption_only]
|
|
92
99
|
@disable_crypt_shared_lib_search = options[:disable_crypt_shared_lib_search]
|
|
@@ -200,6 +207,17 @@ module Mongo
|
|
|
200
207
|
Binding.setopt_bypass_query_analysis(self) if @bypass_query_analysis
|
|
201
208
|
end
|
|
202
209
|
|
|
210
|
+
def set_key_expiration
|
|
211
|
+
unless @key_expiration_ms.is_a?(Integer) && !@key_expiration_ms.negative?
|
|
212
|
+
raise ArgumentError.new(
|
|
213
|
+
"#{@key_expiration_ms} is an invalid key_expiration_ms value; " \
|
|
214
|
+
'must be a non-negative Integer or nil'
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
Binding.setopt_key_expiration(self, @key_expiration_ms)
|
|
219
|
+
end
|
|
220
|
+
|
|
203
221
|
# Send the logs from libmongocrypt to the Mongo::Logger
|
|
204
222
|
def set_logger_callback
|
|
205
223
|
@log_callback = proc do |level, msg|
|
data/lib/mongo/cursor.rb
CHANGED
|
@@ -103,6 +103,19 @@ module Mongo
|
|
|
103
103
|
self.class.finalize(kill_spec(@connection_global_id), cluster)
|
|
104
104
|
)
|
|
105
105
|
end
|
|
106
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
107
|
+
# In load-balanced topology the connection of the initial result is
|
|
108
|
+
# checked out of the pool until the cursor is drained. If the cursor
|
|
109
|
+
# cannot be constructed, nothing will ever check the connection back
|
|
110
|
+
# in, so release it here before the error propagates. Exception (not
|
|
111
|
+
# StandardError) is rescued so that an interrupt does not permanently
|
|
112
|
+
# leak the connection. In other topologies the connection is not owned
|
|
113
|
+
# by the cursor and must not be touched here.
|
|
114
|
+
if server&.load_balancer? && result.is_a?(Operation::Result) &&
|
|
115
|
+
(connection = result.connection)
|
|
116
|
+
connection.connection_pool.check_in_if_checked_out(connection)
|
|
117
|
+
end
|
|
118
|
+
raise
|
|
106
119
|
end
|
|
107
120
|
|
|
108
121
|
# @api private
|
data/lib/mongo/database/view.rb
CHANGED
|
@@ -273,12 +273,13 @@ module Mongo
|
|
|
273
273
|
execution_opts = {}
|
|
274
274
|
execution_opts[:deserialize_as_bson] = opts.delete(:deserialize_as_bson) if opts.key?(:deserialize_as_bson)
|
|
275
275
|
if server.load_balancer?
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
276
|
+
server.pool.with_cursor_connection(context: context) do |connection|
|
|
277
|
+
initial_query_op(session, opts).execute_with_connection(
|
|
278
|
+
connection,
|
|
279
|
+
context: context,
|
|
280
|
+
options: execution_opts
|
|
281
|
+
)
|
|
282
|
+
end
|
|
282
283
|
else
|
|
283
284
|
initial_query_op(session, opts).execute(
|
|
284
285
|
server,
|
data/lib/mongo/database.rb
CHANGED
|
@@ -339,15 +339,12 @@ module Mongo
|
|
|
339
339
|
server = selector.select_server(cluster, nil, session)
|
|
340
340
|
if server.load_balancer?
|
|
341
341
|
# The connection is checked in by the cursor when it is drained.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
connection.connection_pool.check_in(connection) unless connection.pinned?
|
|
349
|
-
connection = nil
|
|
350
|
-
raise
|
|
342
|
+
# If the command fails, the connection is released before the
|
|
343
|
+
# error propagates so that a retried attempt checks out a
|
|
344
|
+
# fresh one.
|
|
345
|
+
server.pool.with_cursor_connection(context: context) do |conn|
|
|
346
|
+
connection = conn
|
|
347
|
+
op.execute_with_connection(conn, context: context, options: execution_opts)
|
|
351
348
|
end
|
|
352
349
|
else
|
|
353
350
|
op.execute(server, context: context, options: execution_opts)
|
|
@@ -366,7 +363,7 @@ module Mongo
|
|
|
366
363
|
# If the cursor was created it owns the session and connection;
|
|
367
364
|
# otherwise (error or no cursor in the response) release them here.
|
|
368
365
|
unless cursor
|
|
369
|
-
connection.connection_pool.
|
|
366
|
+
connection.connection_pool.check_in_if_checked_out(connection) if connection
|
|
370
367
|
session.end_session if session && session.implicit?
|
|
371
368
|
end
|
|
372
369
|
end
|
|
@@ -18,24 +18,6 @@ module Mongo
|
|
|
18
18
|
class Error
|
|
19
19
|
# Raised if an unsupported option is specified for an operation.
|
|
20
20
|
class UnsupportedOption < Error
|
|
21
|
-
# The error message provided when the user passes the hint option to
|
|
22
|
-
# a write operation against a server that does not support the hint
|
|
23
|
-
# option and does not provide option validation.
|
|
24
|
-
#
|
|
25
|
-
# @api private
|
|
26
|
-
HINT_MESSAGE = 'The MongoDB server handling this request does not support ' \
|
|
27
|
-
'the hint option on this command. The hint option is supported on update ' \
|
|
28
|
-
'commands on MongoDB server versions 4.2 and later and on findAndModify ' \
|
|
29
|
-
'and delete commands on MongoDB server versions 4.4 and later'
|
|
30
|
-
|
|
31
|
-
# The error message provided when the user passes the hint option to
|
|
32
|
-
# an unacknowledged write operation.
|
|
33
|
-
#
|
|
34
|
-
# @api private
|
|
35
|
-
UNACKNOWLEDGED_HINT_MESSAGE = 'The hint option cannot be specified on ' \
|
|
36
|
-
'an unacknowledged write operation. Remove the hint option or perform ' \
|
|
37
|
-
'this operation with a write concern of at least { w: 1 }'
|
|
38
|
-
|
|
39
21
|
# The error message provided when the user passes the allow_disk_use
|
|
40
22
|
# option to a find operation against a server that does not support the
|
|
41
23
|
# allow_disk_use operation and does not provide option validation.
|
|
@@ -56,28 +38,6 @@ module Mongo
|
|
|
56
38
|
'option is supported on createIndexes commands on MongoDB server versions ' \
|
|
57
39
|
'4.4 and later'
|
|
58
40
|
|
|
59
|
-
# Raise an error about an unsupported hint option.
|
|
60
|
-
#
|
|
61
|
-
# @option options [ Boolean ] unacknowledged_write Whether this error
|
|
62
|
-
# pertains to a hint option passed to an unacknowledged write. Defaults
|
|
63
|
-
# to false.
|
|
64
|
-
#
|
|
65
|
-
# @return [ Mongo::Error::UnsupportedOption ] An error with a default
|
|
66
|
-
# error message.
|
|
67
|
-
#
|
|
68
|
-
# @api private
|
|
69
|
-
def self.hint_error(**options)
|
|
70
|
-
unacknowledged_write = options[:unacknowledged_write] || false
|
|
71
|
-
|
|
72
|
-
error_message = if unacknowledged_write
|
|
73
|
-
UNACKNOWLEDGED_HINT_MESSAGE
|
|
74
|
-
else
|
|
75
|
-
HINT_MESSAGE
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
new(error_message)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
41
|
# Raise an error about an unsupported allow_disk_use option.
|
|
82
42
|
#
|
|
83
43
|
# @return [ Mongo::Error::UnsupportedOption ] An error with a default
|
data/lib/mongo/grid/fs_bucket.rb
CHANGED
|
@@ -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
|
data/lib/mongo/index/view.rb
CHANGED
|
@@ -421,8 +421,9 @@ module Mongo
|
|
|
421
421
|
|
|
422
422
|
def send_initial_query(op, server, _session, context)
|
|
423
423
|
if server.load_balancer?
|
|
424
|
-
|
|
425
|
-
|
|
424
|
+
server.pool.with_cursor_connection(context: context) do |connection|
|
|
425
|
+
op.execute_with_connection(connection, context: context)
|
|
426
|
+
end
|
|
426
427
|
else
|
|
427
428
|
op.execute(server, context: context)
|
|
428
429
|
end
|
|
@@ -37,6 +37,11 @@ module Mongo
|
|
|
37
37
|
copydb
|
|
38
38
|
].freeze
|
|
39
39
|
|
|
40
|
+
# The hello and legacy hello command names. A constant, because
|
|
41
|
+
# sensitive? runs on every command event and an array literal would
|
|
42
|
+
# allocate on each call.
|
|
43
|
+
HELLO_COMMANDS = %w[hello ismaster isMaster].freeze
|
|
44
|
+
|
|
40
45
|
# Check whether the command is sensitive in terms of command monitoring
|
|
41
46
|
# spec. A command is detected as sensitive if it is in the
|
|
42
47
|
# list or if it is a hello/legacy hello command, and
|
|
@@ -53,7 +58,7 @@ module Mongo
|
|
|
53
58
|
# when speculativeAuthenticate is present, their commands AND replies
|
|
54
59
|
# MUST be redacted from the events.
|
|
55
60
|
# See https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.md#security
|
|
56
|
-
|
|
61
|
+
HELLO_COMMANDS.include?(command_name.to_s) &&
|
|
57
62
|
!!document['speculativeAuthenticate']
|
|
58
63
|
end
|
|
59
64
|
|
|
@@ -80,7 +80,7 @@ module Mongo
|
|
|
80
80
|
# @since 2.0.0
|
|
81
81
|
# @api public
|
|
82
82
|
def successful?
|
|
83
|
-
!documents.nil?
|
|
83
|
+
!documents.nil? && parser.message.empty?
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
# Get the execution time of the map/reduce.
|
|
@@ -112,7 +112,11 @@ module Mongo
|
|
|
112
112
|
# @since 2.0.0
|
|
113
113
|
# @api private
|
|
114
114
|
def validate!
|
|
115
|
-
documents.nil? ?
|
|
115
|
+
if documents.nil? || !parser.message.empty?
|
|
116
|
+
raise_operation_failure
|
|
117
|
+
else
|
|
118
|
+
self
|
|
119
|
+
end
|
|
116
120
|
end
|
|
117
121
|
|
|
118
122
|
# Get the cursor id.
|
|
@@ -456,6 +456,15 @@ module Mongo
|
|
|
456
456
|
doc['cursor']&.[]('atClusterTime') || doc['atClusterTime']
|
|
457
457
|
end
|
|
458
458
|
|
|
459
|
+
# Returns the base backoff in milliseconds for a server overload error, if present.
|
|
460
|
+
#
|
|
461
|
+
# @return [ Integer | nil ] The base backoff in milliseconds.
|
|
462
|
+
#
|
|
463
|
+
# @api private
|
|
464
|
+
def base_backoff_ms
|
|
465
|
+
first_document && first_document['baseBackoffMS']
|
|
466
|
+
end
|
|
467
|
+
|
|
459
468
|
private
|
|
460
469
|
|
|
461
470
|
def operation_failure_class
|
|
@@ -132,12 +132,7 @@ module Mongo
|
|
|
132
132
|
def process_result_for_sdam(result, connection)
|
|
133
133
|
if (result.not_master? || result.node_recovering?) &&
|
|
134
134
|
connection.generation >= connection.server.pool.generation(service_id: connection.service_id)
|
|
135
|
-
keep_pool =
|
|
136
|
-
false
|
|
137
|
-
else
|
|
138
|
-
# Max wire version needs to be examined while the server is known
|
|
139
|
-
connection.description.server_version_gte?('4.2')
|
|
140
|
-
end
|
|
135
|
+
keep_pool = !result.node_shutting_down?
|
|
141
136
|
|
|
142
137
|
connection.server.unknown!(
|
|
143
138
|
keep_connection_pool: keep_pool,
|
|
@@ -36,7 +36,7 @@ module Mongo
|
|
|
36
36
|
return unless @replies
|
|
37
37
|
|
|
38
38
|
@replies.reduce(nil) do |errors, reply|
|
|
39
|
-
next unless (write_errors = reply.documents.first['writeErrors'])
|
|
39
|
+
next errors unless (write_errors = reply.documents.first['writeErrors'])
|
|
40
40
|
|
|
41
41
|
wes = write_errors.collect do |we|
|
|
42
42
|
we.merge!('index' => count + we['index'])
|
|
@@ -59,11 +59,11 @@ module Mongo
|
|
|
59
59
|
return unless @replies
|
|
60
60
|
|
|
61
61
|
@replies.each_with_index.reduce(nil) do |errors, (reply, _)|
|
|
62
|
-
next unless (write_concern_errors = reply.documents.first['writeConcernErrors'])
|
|
62
|
+
next errors unless (write_concern_errors = reply.documents.first['writeConcernErrors'])
|
|
63
63
|
|
|
64
64
|
(errors || []) << write_concern_errors.reduce(nil) do |errs, wce|
|
|
65
65
|
wce.merge!('index' => count + wce['index'])
|
|
66
|
-
(errs || []) <<
|
|
66
|
+
(errs || []) << wce
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
end
|
|
@@ -148,10 +148,6 @@ module Mongo
|
|
|
148
148
|
# @return [ Mongo::Protocol::Msg ] The decrypted message, or the original
|
|
149
149
|
# message if decryption was not possible or necessary.
|
|
150
150
|
def maybe_decrypt(_context)
|
|
151
|
-
# TODO: determine if we should be decrypting data coming from pre-4.2
|
|
152
|
-
# servers, potentially using legacy wire protocols. If so we need
|
|
153
|
-
# to implement decryption for those wire protocols as our current
|
|
154
|
-
# encryption/decryption code is OP_MSG-specific.
|
|
155
151
|
self
|
|
156
152
|
end
|
|
157
153
|
|
data/lib/mongo/protocol/msg.rb
CHANGED
|
@@ -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(
|
|
208
|
+
def maybe_encrypt(_connection, context)
|
|
211
209
|
if context.encrypt?
|
|
212
|
-
if connection.description.max_wire_version < 8
|
|
213
|
-
raise Error::CryptError.new(
|
|
214
|
-
'Cannot perform encryption against a MongoDB server older than ' +
|
|
215
|
-
'4.2 (wire version less than 8). Currently connected to server ' +
|
|
216
|
-
"with max wire version #{connection.description.max_wire_version}} " +
|
|
217
|
-
'(Auto-encryption requires a minimum MongoDB version of 4.2)'
|
|
218
|
-
)
|
|
219
|
-
end
|
|
220
|
-
|
|
221
210
|
db_name = @main_document[DATABASE_IDENTIFIER]
|
|
222
211
|
cmd = merge_sections
|
|
223
212
|
enc_cmd = context.encrypt(db_name, cmd)
|
|
@@ -319,16 +319,8 @@ module Mongo
|
|
|
319
319
|
# @return [ Array<BSON::Document> ] Deserialized section.
|
|
320
320
|
#
|
|
321
321
|
# @since 2.5.0
|
|
322
|
-
def self.deserialize(buffer)
|
|
322
|
+
def self.deserialize(buffer, _options = {})
|
|
323
323
|
raise NotImplementedError
|
|
324
|
-
|
|
325
|
-
start_size = buffer.length
|
|
326
|
-
section_size = buffer.get_int32 # get the size
|
|
327
|
-
end_size = start_size - section_size
|
|
328
|
-
buffer.get_cstring # get the identifier
|
|
329
|
-
documents = []
|
|
330
|
-
documents << BSON::Document.from_bson(buffer) until buffer.length == end_size
|
|
331
|
-
documents
|
|
332
324
|
end
|
|
333
325
|
end
|
|
334
326
|
end
|
|
@@ -23,9 +23,24 @@ module Mongo
|
|
|
23
23
|
# a random value. Can be injected for deterministic testing.
|
|
24
24
|
#
|
|
25
25
|
# @return [ Float ] The backoff delay in seconds.
|
|
26
|
-
def self.backoff_delay(attempt, jitter: rand)
|
|
27
|
-
jitter * [ MAX_BACKOFF,
|
|
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)')
|
data/lib/mongo/retryable.rb
CHANGED
|
@@ -129,7 +129,7 @@ module Mongo
|
|
|
129
129
|
|
|
130
130
|
error_count += 1
|
|
131
131
|
policy = client.retry_policy
|
|
132
|
-
delay = policy.backoff_delay(error_count)
|
|
132
|
+
delay = policy.backoff_delay(error_count, err: e)
|
|
133
133
|
raise e unless policy.should_retry_overload?(error_count, delay, context: context)
|
|
134
134
|
|
|
135
135
|
Logger.logger.warn("Overload retry due to: #{e.class.name}: #{e.message}")
|
|
@@ -130,7 +130,6 @@ module Mongo
|
|
|
130
130
|
doc[:driver] = driver_doc
|
|
131
131
|
doc[:os] = os_doc
|
|
132
132
|
doc[:platform] = platform_string
|
|
133
|
-
doc[:backpressure] = true
|
|
134
133
|
env_doc.tap { |env| doc[:env] = env if env }
|
|
135
134
|
end
|
|
136
135
|
end
|
|
@@ -157,7 +156,7 @@ module Mongo
|
|
|
157
156
|
def document
|
|
158
157
|
@document ||= begin
|
|
159
158
|
client = Truncator.new(client_document).document
|
|
160
|
-
BSON::Document.new(compression: @compressors, client: client).tap do |doc|
|
|
159
|
+
BSON::Document.new(compression: @compressors, client: client, backpressure: '2').tap do |doc|
|
|
161
160
|
doc[:saslSupportedMechs] = @request_auth_mech if @request_auth_mech
|
|
162
161
|
doc.update(Utils.transform_server_api(@server_api)) if @server_api
|
|
163
162
|
end
|