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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/Rakefile +27 -154
- data/lib/mongo/cluster/topology/base.rb +16 -0
- data/lib/mongo/cluster.rb +27 -1
- data/lib/mongo/collection/view/iterable.rb +1 -0
- data/lib/mongo/collection.rb +27 -3
- data/lib/mongo/error/transactions_not_supported.rb +34 -0
- data/lib/mongo/error.rb +1 -0
- data/lib/mongo/grid/fs_bucket.rb +6 -0
- data/lib/mongo/monitoring/event/secure.rb +1 -1
- data/lib/mongo/operation/create_search_indexes/op_msg.rb +31 -0
- data/lib/mongo/operation/create_search_indexes.rb +15 -0
- data/lib/mongo/operation/drop_search_index/op_msg.rb +33 -0
- data/lib/mongo/operation/drop_search_index.rb +15 -0
- data/lib/mongo/operation/shared/executable.rb +43 -27
- data/lib/mongo/operation/shared/response_handling.rb +23 -25
- data/lib/mongo/operation/shared/specifiable.rb +7 -0
- data/lib/mongo/operation/update_search_index/op_msg.rb +34 -0
- data/lib/mongo/operation/update_search_index.rb +15 -0
- data/lib/mongo/operation.rb +3 -0
- data/lib/mongo/retryable/read_worker.rb +7 -6
- data/lib/mongo/retryable/write_worker.rb +7 -4
- data/lib/mongo/retryable.rb +2 -2
- data/lib/mongo/search_index/view.rb +232 -0
- data/lib/mongo/server/app_metadata/environment.rb +64 -9
- data/lib/mongo/server/app_metadata.rb +5 -4
- data/lib/mongo/server/description/features.rb +1 -0
- data/lib/mongo/server_selector/base.rb +32 -6
- data/lib/mongo/session/server_session/dirtyable.rb +52 -0
- data/lib/mongo/session/server_session.rb +3 -0
- data/lib/mongo/session/session_pool.rb +12 -18
- data/lib/mongo/session.rb +32 -0
- data/lib/mongo/uri.rb +0 -4
- data/lib/mongo/version.rb +1 -1
- data/lib/mongo.rb +1 -0
- data/mongo.gemspec +1 -7
- data/spec/atlas/atlas_connectivity_spec.rb +5 -9
- data/spec/atlas/operations_spec.rb +1 -5
- data/spec/faas/ruby-sam-app/Gemfile +9 -0
- data/spec/faas/ruby-sam-app/mongodb/Gemfile +4 -0
- data/spec/faas/ruby-sam-app/mongodb/app.rb +149 -0
- data/spec/faas/ruby-sam-app/template.yaml +48 -0
- data/spec/integration/client_side_encryption/corpus_spec.rb +10 -2
- data/spec/integration/retryable_reads_errors_spec.rb +161 -8
- data/spec/integration/retryable_writes_errors_spec.rb +156 -0
- data/spec/integration/search_indexes_prose_spec.rb +168 -0
- data/spec/lite_spec_helper.rb +32 -10
- data/spec/mongo/cluster_spec.rb +36 -0
- data/spec/mongo/collection/view/aggregation_spec.rb +6 -1
- data/spec/mongo/collection/view/explainable_spec.rb +2 -0
- data/spec/mongo/collection_crud_spec.rb +1 -1
- data/spec/mongo/operation/insert_spec.rb +1 -1
- data/spec/mongo/retryable/write_worker_spec.rb +39 -0
- data/spec/mongo/server/app_metadata/environment_spec.rb +135 -0
- data/spec/mongo/server/app_metadata_spec.rb +12 -2
- data/spec/mongo/server/connection_spec.rb +4 -0
- data/spec/mongo/session/session_pool_spec.rb +1 -16
- data/spec/mongo/session_transaction_spec.rb +15 -0
- data/spec/mongo/uri_spec.rb +0 -9
- data/spec/runners/crud/test.rb +0 -8
- data/spec/runners/crud.rb +1 -1
- data/spec/runners/transactions/test.rb +12 -3
- data/spec/runners/unified/assertions.rb +16 -3
- data/spec/runners/unified/crud_operations.rb +12 -0
- data/spec/runners/unified/search_index_operations.rb +63 -0
- data/spec/runners/unified/support_operations.rb +3 -5
- data/spec/runners/unified/test.rb +11 -2
- data/spec/shared/lib/mrss/docker_runner.rb +3 -0
- data/spec/shared/share/Dockerfile.erb +20 -69
- data/spec/shared/shlib/server.sh +1 -0
- data/spec/shared/shlib/set_env.sh +5 -28
- data/spec/spec_helper.rb +1 -1
- data/spec/spec_tests/data/client_side_encryption/explain.yml +2 -2
- data/spec/spec_tests/data/connection_string/invalid-uris.yml +0 -10
- data/spec/spec_tests/data/connection_string/valid-options.yml +13 -0
- data/spec/spec_tests/data/crud_unified/find-test-all-options.yml +348 -0
- data/spec/spec_tests/data/index_management/createSearchIndex.yml +64 -0
- data/spec/spec_tests/data/index_management/createSearchIndexes.yml +86 -0
- data/spec/spec_tests/data/index_management/dropSearchIndex.yml +43 -0
- data/spec/spec_tests/data/index_management/listSearchIndexes.yml +91 -0
- data/spec/spec_tests/data/index_management/updateSearchIndex.yml +46 -0
- data/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml +3 -6
- data/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml +3 -6
- data/spec/spec_tests/data/run_command_unified/runCommand.yml +319 -0
- data/spec/spec_tests/data/sessions_unified/driver-sessions-dirty-session-errors.yml +351 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +7 -7
- data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +3 -4
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +3 -3
- data/spec/spec_tests/index_management_unified_spec.rb +13 -0
- data/spec/spec_tests/run_command_unified_spec.rb +13 -0
- data/spec/spec_tests/sdam_unified_spec.rb +2 -0
- data/spec/support/constraints.rb +6 -0
- data/spec/support/faas/app/aws_lambda/mongodb/Gemfile.lock +19 -0
- data/spec/support/ocsp +1 -1
- data/spec/support/recording_logger.rb +27 -0
- data/spec/support/spec_config.rb +5 -0
- data.tar.gz.sig +0 -0
- metadata +1329 -1285
- metadata.gz.sig +3 -2
- data/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml +0 -49
|
@@ -260,6 +260,13 @@ module Mongo
|
|
|
260
260
|
spec[INDEX]
|
|
261
261
|
end
|
|
262
262
|
|
|
263
|
+
# Get the index id from the spec.
|
|
264
|
+
#
|
|
265
|
+
# @return [ String ] The index id.
|
|
266
|
+
def index_id
|
|
267
|
+
spec[:index_id]
|
|
268
|
+
end
|
|
269
|
+
|
|
263
270
|
# Get the index name from the spec.
|
|
264
271
|
#
|
|
265
272
|
# @example Get the index name.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mongo
|
|
4
|
+
module Operation
|
|
5
|
+
class UpdateSearchIndex
|
|
6
|
+
# A MongoDB updateSearchIndex operation sent as an op message.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
class OpMsg < OpMsgBase
|
|
10
|
+
include ExecutableTransactionLabel
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
# Returns the command to send to the database, describing the
|
|
15
|
+
# desired updateSearchIndex operation.
|
|
16
|
+
#
|
|
17
|
+
# @param [ Mongo::Server ] _server the server that will receive the
|
|
18
|
+
# command
|
|
19
|
+
#
|
|
20
|
+
# @return [ Hash ] the selector
|
|
21
|
+
def selector(_server)
|
|
22
|
+
{
|
|
23
|
+
updateSearchIndex: coll_name,
|
|
24
|
+
:$db => db_name,
|
|
25
|
+
definition: index,
|
|
26
|
+
}.tap do |sel|
|
|
27
|
+
sel[:id] = index_id if index_id
|
|
28
|
+
sel[:name] = index_name if index_name
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mongo/operation/update_search_index/op_msg'
|
|
4
|
+
|
|
5
|
+
module Mongo
|
|
6
|
+
module Operation
|
|
7
|
+
# A MongoDB updateSearchIndex command operation.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
class UpdateSearchIndex
|
|
11
|
+
include Specifiable
|
|
12
|
+
include OpMsgExecutable
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/mongo/operation.rb
CHANGED
|
@@ -51,6 +51,9 @@ require 'mongo/operation/update_user'
|
|
|
51
51
|
require 'mongo/operation/remove_user'
|
|
52
52
|
require 'mongo/operation/create_index'
|
|
53
53
|
require 'mongo/operation/drop_index'
|
|
54
|
+
require 'mongo/operation/create_search_indexes'
|
|
55
|
+
require 'mongo/operation/drop_search_index'
|
|
56
|
+
require 'mongo/operation/update_search_index'
|
|
54
57
|
|
|
55
58
|
module Mongo
|
|
56
59
|
|
|
@@ -190,12 +190,13 @@ module Mongo
|
|
|
190
190
|
#
|
|
191
191
|
# @return [ Result ] The result of the operation.
|
|
192
192
|
def modern_read_with_retry(session, server_selector, &block)
|
|
193
|
-
|
|
193
|
+
server = select_server(cluster, server_selector, session)
|
|
194
|
+
yield server
|
|
194
195
|
rescue *retryable_exceptions, Error::OperationFailure, Auth::Unauthorized, Error::PoolError => e
|
|
195
196
|
e.add_notes('modern retry', 'attempt 1')
|
|
196
197
|
raise e if session.in_transaction?
|
|
197
198
|
raise e if !is_retryable_exception?(e) && !e.write_retryable?
|
|
198
|
-
retry_read(e, session, server_selector, &block)
|
|
199
|
+
retry_read(e, session, server_selector, failed_server: server, &block)
|
|
199
200
|
end
|
|
200
201
|
|
|
201
202
|
# Attempts to do a "legacy" read with retry. The operation will be
|
|
@@ -257,12 +258,14 @@ module Mongo
|
|
|
257
258
|
# being run on.
|
|
258
259
|
# @param [ Mongo::ServerSelector::Selectable ] server_selector Server
|
|
259
260
|
# selector for the operation.
|
|
261
|
+
# @param [ Mongo::Server ] failed_server The server on which the original
|
|
262
|
+
# operation failed.
|
|
260
263
|
# @param [ Proc ] block The block to execute.
|
|
261
264
|
#
|
|
262
265
|
# @return [ Result ] The result of the operation.
|
|
263
|
-
def retry_read(original_error, session, server_selector, &block)
|
|
266
|
+
def retry_read(original_error, session, server_selector, failed_server: nil, &block)
|
|
264
267
|
begin
|
|
265
|
-
server = select_server(cluster, server_selector, session)
|
|
268
|
+
server = select_server(cluster, server_selector, session, failed_server)
|
|
266
269
|
rescue Error, Error::AuthError => e
|
|
267
270
|
original_error.add_note("later retry failed: #{e.class}: #{e}")
|
|
268
271
|
raise original_error
|
|
@@ -289,8 +292,6 @@ module Mongo
|
|
|
289
292
|
raise original_error
|
|
290
293
|
end
|
|
291
294
|
end
|
|
292
|
-
|
|
293
295
|
end
|
|
294
|
-
|
|
295
296
|
end
|
|
296
297
|
end
|
|
@@ -103,8 +103,9 @@ module Mongo
|
|
|
103
103
|
def nro_write_with_retry(write_concern, context:, &block)
|
|
104
104
|
session = context.session
|
|
105
105
|
server = select_server(cluster, ServerSelector.primary, session)
|
|
106
|
+
options = session&.client&.options || {}
|
|
106
107
|
|
|
107
|
-
if
|
|
108
|
+
if options[:retry_writes]
|
|
108
109
|
begin
|
|
109
110
|
server.with_connection(connection_global_id: context.connection_global_id) do |connection|
|
|
110
111
|
yield connection, nil, context
|
|
@@ -240,7 +241,7 @@ module Mongo
|
|
|
240
241
|
|
|
241
242
|
# Context#with creates a new context, which is not necessary here
|
|
242
243
|
# but the API is less prone to misuse this way.
|
|
243
|
-
retry_write(e, txn_num, context: context.with(is_retry: true), &block)
|
|
244
|
+
retry_write(e, txn_num, context: context.with(is_retry: true), failed_server: server, &block)
|
|
244
245
|
end
|
|
245
246
|
|
|
246
247
|
# Called after a failed write, this will retry the write no more than
|
|
@@ -250,9 +251,11 @@ module Mongo
|
|
|
250
251
|
# retry.
|
|
251
252
|
# @param [ Number ] txn_num The transaction number.
|
|
252
253
|
# @param [ Operation::Context ] context The context for the operation.
|
|
254
|
+
# @param [ Mongo::Server ] failed_server The server on which the original
|
|
255
|
+
# operation failed.
|
|
253
256
|
#
|
|
254
257
|
# @return [ Result ] The result of the operation.
|
|
255
|
-
def retry_write(original_error, txn_num, context:, &block)
|
|
258
|
+
def retry_write(original_error, txn_num, context:, failed_server: nil, &block)
|
|
256
259
|
session = context.session
|
|
257
260
|
|
|
258
261
|
# We do not request a scan of the cluster here, because error handling
|
|
@@ -260,7 +263,7 @@ module Mongo
|
|
|
260
263
|
# server description and/or topology as necessary (specifically,
|
|
261
264
|
# a socket error or a not master error should have marked the respective
|
|
262
265
|
# server unknown). Here we just need to wait for server selection.
|
|
263
|
-
server = select_server(cluster, ServerSelector.primary, session)
|
|
266
|
+
server = select_server(cluster, ServerSelector.primary, session, failed_server)
|
|
264
267
|
|
|
265
268
|
unless server.retry_writes?
|
|
266
269
|
# Do not need to add "modern retry" here, it should already be on
|
data/lib/mongo/retryable.rb
CHANGED
|
@@ -46,8 +46,8 @@ module Mongo
|
|
|
46
46
|
# @api private
|
|
47
47
|
#
|
|
48
48
|
# @return [ Mongo::Server ] A server matching the server preference.
|
|
49
|
-
def select_server(cluster, server_selector, session)
|
|
50
|
-
server_selector.select_server(cluster, nil, session)
|
|
49
|
+
def select_server(cluster, server_selector, session, failed_server = nil)
|
|
50
|
+
server_selector.select_server(cluster, nil, session, deprioritized: [failed_server].compact)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
# Returns the read worker for handling retryable reads.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mongo
|
|
4
|
+
module SearchIndex
|
|
5
|
+
# A class representing a view of search indexes.
|
|
6
|
+
class View
|
|
7
|
+
include Enumerable
|
|
8
|
+
include Retryable
|
|
9
|
+
include Collection::Helpers
|
|
10
|
+
|
|
11
|
+
# @return [ Mongo::Collection ] the collection this view belongs to
|
|
12
|
+
attr_reader :collection
|
|
13
|
+
|
|
14
|
+
# @return [ nil | String ] the index id to query
|
|
15
|
+
attr_reader :requested_index_id
|
|
16
|
+
|
|
17
|
+
# @return [ nil | String ] the index name to query
|
|
18
|
+
attr_reader :requested_index_name
|
|
19
|
+
|
|
20
|
+
# @return [ Hash ] the options hash to use for the aggregate command
|
|
21
|
+
# when querying the available indexes.
|
|
22
|
+
attr_reader :aggregate_options
|
|
23
|
+
|
|
24
|
+
# Create the new search index view.
|
|
25
|
+
#
|
|
26
|
+
# @param [ Collection ] collection The collection.
|
|
27
|
+
# @param [ Hash ] options The options that configure the behavior of the view.
|
|
28
|
+
#
|
|
29
|
+
# @option options [ String ] :id The specific index id to query (optional)
|
|
30
|
+
# @option options [ String ] :name The name of the specific index to query (optional)
|
|
31
|
+
# @option options [ Hash ] :aggregate The options hash to send to the
|
|
32
|
+
# aggregate command when querying the available indexes.
|
|
33
|
+
def initialize(collection, options = {})
|
|
34
|
+
@collection = collection
|
|
35
|
+
@requested_index_id = options[:id]
|
|
36
|
+
@requested_index_name = options[:name]
|
|
37
|
+
@aggregate_options = options[:aggregate] || {}
|
|
38
|
+
|
|
39
|
+
return if @aggregate_options.is_a?(Hash)
|
|
40
|
+
|
|
41
|
+
raise ArgumentError, "The :aggregate option must be a Hash (got a #{@aggregate_options.class})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Create a single search index with the given definition. If the name is
|
|
45
|
+
# provided, the new index will be given that name.
|
|
46
|
+
#
|
|
47
|
+
# @param [ Hash ] definition The definition of the search index.
|
|
48
|
+
# @param [ nil | String ] name The name to give the new search index.
|
|
49
|
+
#
|
|
50
|
+
# @return [ String ] the name of the new search index.
|
|
51
|
+
def create_one(definition, name: nil)
|
|
52
|
+
create_many([ { name: name, definition: definition } ]).first
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Create multiple search indexes with a single command.
|
|
56
|
+
#
|
|
57
|
+
# @param [ Array<Hash> ] indexes The description of the indexes to
|
|
58
|
+
# create. Each element of the list must be a hash with a definition
|
|
59
|
+
# key, and an optional name key.
|
|
60
|
+
#
|
|
61
|
+
# @return [ Array<String> ] the names of the new search indexes.
|
|
62
|
+
def create_many(indexes)
|
|
63
|
+
spec = spec_with(indexes: indexes.map { |v| validate_search_index!(v) })
|
|
64
|
+
result = Operation::CreateSearchIndexes.new(spec).execute(next_primary, context: execution_context)
|
|
65
|
+
result.first['indexesCreated'].map { |idx| idx['name'] }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Drop the search index with the given id, or name. One or the other must
|
|
69
|
+
# be specified, but not both.
|
|
70
|
+
#
|
|
71
|
+
# @param [ String ] id the id of the index to drop
|
|
72
|
+
# @param [ String ] name the name of the index to drop
|
|
73
|
+
#
|
|
74
|
+
# @return [ Mongo::Operation::Result | false ] the result of the
|
|
75
|
+
# operation, or false if the given index does not exist.
|
|
76
|
+
def drop_one(id: nil, name: nil)
|
|
77
|
+
validate_id_or_name!(id, name)
|
|
78
|
+
|
|
79
|
+
spec = spec_with(index_id: id, index_name: name)
|
|
80
|
+
op = Operation::DropSearchIndex.new(spec)
|
|
81
|
+
|
|
82
|
+
# per the spec:
|
|
83
|
+
# Drivers MUST suppress NamespaceNotFound errors for the
|
|
84
|
+
# ``dropSearchIndex`` helper. Drop operations should be idempotent.
|
|
85
|
+
do_drop(op, nil, execution_context)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Iterate over the search indexes.
|
|
89
|
+
#
|
|
90
|
+
# @param [ Proc ] block if given, each search index will be yieleded to
|
|
91
|
+
# the block.
|
|
92
|
+
#
|
|
93
|
+
# @return [ self | Enumerator ] if a block is given, self is returned.
|
|
94
|
+
# Otherwise, an enumerator will be returned.
|
|
95
|
+
def each(&block)
|
|
96
|
+
@result ||= begin
|
|
97
|
+
spec = {}.tap do |s|
|
|
98
|
+
s[:id] = requested_index_id if requested_index_id
|
|
99
|
+
s[:name] = requested_index_name if requested_index_name
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
collection.aggregate(
|
|
103
|
+
[ { '$listSearchIndexes' => spec } ],
|
|
104
|
+
aggregate_options
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
return @result.to_enum unless block
|
|
109
|
+
|
|
110
|
+
@result.each(&block)
|
|
111
|
+
self
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Update the search index with the given id or name. One or the other
|
|
115
|
+
# must be provided, but not both.
|
|
116
|
+
#
|
|
117
|
+
# @param [ Hash ] definition the definition to replace the given search
|
|
118
|
+
# index with.
|
|
119
|
+
# @param [ nil | String ] id the id of the search index to update
|
|
120
|
+
# @param [ nil | String ] name the name of the search index to update
|
|
121
|
+
#
|
|
122
|
+
# @return [ Mongo::Operation::Result ] the result of the operation
|
|
123
|
+
def update_one(definition, id: nil, name: nil)
|
|
124
|
+
validate_id_or_name!(id, name)
|
|
125
|
+
|
|
126
|
+
spec = spec_with(index_id: id, index_name: name, index: definition)
|
|
127
|
+
Operation::UpdateSearchIndex.new(spec).execute(next_primary, context: execution_context)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# The following methods are to make the view act more like an array,
|
|
131
|
+
# without having to explicitly make it an array...
|
|
132
|
+
|
|
133
|
+
# Queries whether the search index enumerable is empty.
|
|
134
|
+
#
|
|
135
|
+
# @return [ true | false ] whether the enumerable is empty or not.
|
|
136
|
+
def empty?
|
|
137
|
+
count.zero?
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
# A helper method for building the specification document with certain
|
|
143
|
+
# values pre-populated.
|
|
144
|
+
#
|
|
145
|
+
# @param [ Hash ] extras the values to put into the specification
|
|
146
|
+
#
|
|
147
|
+
# @return [ Hash ] the specification document
|
|
148
|
+
def spec_with(extras)
|
|
149
|
+
{
|
|
150
|
+
coll_name: collection.name,
|
|
151
|
+
db_name: collection.database.name,
|
|
152
|
+
}.merge(extras)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# A helper method for retrieving the primary server from the cluster.
|
|
156
|
+
#
|
|
157
|
+
# @return [ Mongo::Server ] the server to use
|
|
158
|
+
def next_primary(ping = nil, session = nil)
|
|
159
|
+
collection.cluster.next_primary(ping, session)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# A helper method for constructing a new operation context for executing
|
|
163
|
+
# an operation.
|
|
164
|
+
#
|
|
165
|
+
# @return [ Mongo::Operation::Context ] the operation context
|
|
166
|
+
def execution_context
|
|
167
|
+
Operation::Context.new(client: collection.client)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Validates the given id and name, ensuring that exactly one of them
|
|
171
|
+
# is non-nil.
|
|
172
|
+
#
|
|
173
|
+
# @param [ nil | String ] id the id to validate
|
|
174
|
+
# @param [ nil | String ] name the name to validate
|
|
175
|
+
#
|
|
176
|
+
# @raise [ ArgumentError ] if neither or both arguments are nil
|
|
177
|
+
def validate_id_or_name!(id, name)
|
|
178
|
+
return unless (id.nil? && name.nil?) || (!id.nil? && !name.nil?)
|
|
179
|
+
|
|
180
|
+
raise ArgumentError, 'exactly one of id or name must be specified'
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Validates the given search index document, ensuring that it has no
|
|
184
|
+
# extra keys, and that the name and definition are valid.
|
|
185
|
+
#
|
|
186
|
+
# @param [ Hash ] doc the document to validate
|
|
187
|
+
#
|
|
188
|
+
# @raise [ ArgumentError ] if the document is invalid.
|
|
189
|
+
def validate_search_index!(doc)
|
|
190
|
+
validate_search_index_keys!(doc.keys)
|
|
191
|
+
validate_search_index_name!(doc[:name] || doc['name'])
|
|
192
|
+
validate_search_index_definition!(doc[:definition] || doc['definition'])
|
|
193
|
+
doc
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Validates the keys of a search index document, ensuring that
|
|
197
|
+
# they are all valid.
|
|
198
|
+
#
|
|
199
|
+
# @param [ Array<String | Hash> ] keys the keys of a search index document
|
|
200
|
+
#
|
|
201
|
+
# @raise [ ArgumentError ] if the list contains any invalid keys
|
|
202
|
+
def validate_search_index_keys!(keys)
|
|
203
|
+
extras = keys - [ 'name', 'definition', :name, :definition ]
|
|
204
|
+
|
|
205
|
+
raise ArgumentError, "invalid keys in search index creation: #{extras.inspect}" if extras.any?
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Validates the name of a search index, ensuring that it is either a
|
|
209
|
+
# String or nil.
|
|
210
|
+
#
|
|
211
|
+
# @param [ nil | String ] name the name of a search index
|
|
212
|
+
#
|
|
213
|
+
# @raise [ ArgumentError ] if the name is not valid
|
|
214
|
+
def validate_search_index_name!(name)
|
|
215
|
+
return if name.nil? || name.is_a?(String)
|
|
216
|
+
|
|
217
|
+
raise ArgumentError, "search index name must be nil or a string (got #{name.inspect})"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Validates the definition of a search index.
|
|
221
|
+
#
|
|
222
|
+
# @param [ Hash ] definition the definition of a search index
|
|
223
|
+
#
|
|
224
|
+
# @raise [ ArgumentError ] if the definition is not valid
|
|
225
|
+
def validate_search_index_definition!(definition)
|
|
226
|
+
return if definition.is_a?(Hash)
|
|
227
|
+
|
|
228
|
+
raise ArgumentError, "search index definition must be a Hash (got #{definition.inspect})"
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -18,9 +18,12 @@ module Mongo
|
|
|
18
18
|
class Server
|
|
19
19
|
class AppMetadata
|
|
20
20
|
# Implements the logic from the handshake spec, for deducing and
|
|
21
|
-
# reporting the current
|
|
21
|
+
# reporting the current environment in which the program is
|
|
22
22
|
# executing.
|
|
23
23
|
#
|
|
24
|
+
# This includes FaaS environment checks, as well as checks for the
|
|
25
|
+
# presence of a container (Docker) and/or orchestrator (Kubernetes).
|
|
26
|
+
#
|
|
24
27
|
# @api private
|
|
25
28
|
class Environment
|
|
26
29
|
# Error class for reporting that too many discriminators were found
|
|
@@ -39,6 +42,10 @@ module Mongo
|
|
|
39
42
|
# Error class for reporting that the value for a field is too long.
|
|
40
43
|
class ValueTooLong < Mongo::Error; end
|
|
41
44
|
|
|
45
|
+
# The name and location of the .dockerenv file that will signal the
|
|
46
|
+
# presence of Docker.
|
|
47
|
+
DOCKERENV_PATH = '/.dockerenv'
|
|
48
|
+
|
|
42
49
|
# This value is not explicitly specified in the spec, only implied to be
|
|
43
50
|
# less than 512.
|
|
44
51
|
MAXIMUM_VALUE_LENGTH = 500
|
|
@@ -102,9 +109,11 @@ module Mongo
|
|
|
102
109
|
# if the environment contains invalid or contradictory state, it will
|
|
103
110
|
# be initialized with {{name}} set to {{nil}}.
|
|
104
111
|
def initialize
|
|
112
|
+
@fields = {}
|
|
105
113
|
@error = nil
|
|
106
114
|
@name = detect_environment
|
|
107
|
-
|
|
115
|
+
populate_faas_fields
|
|
116
|
+
detect_container
|
|
108
117
|
rescue TooManyEnvironments => e
|
|
109
118
|
self.error = "too many environments detected: #{e.message}"
|
|
110
119
|
rescue MissingVariable => e
|
|
@@ -115,6 +124,23 @@ module Mongo
|
|
|
115
124
|
self.error = "value for #{e.message} is too long"
|
|
116
125
|
end
|
|
117
126
|
|
|
127
|
+
# Queries the detected container information.
|
|
128
|
+
#
|
|
129
|
+
# @return [ Hash | nil ] the detected container information, or
|
|
130
|
+
# nil if no container was detected.
|
|
131
|
+
def container
|
|
132
|
+
fields[:container]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Queries whether any environment information was able to be
|
|
136
|
+
# detected.
|
|
137
|
+
#
|
|
138
|
+
# @return [ true | false ] if any environment information was
|
|
139
|
+
# detected.
|
|
140
|
+
def present?
|
|
141
|
+
@name || fields.any?
|
|
142
|
+
end
|
|
143
|
+
|
|
118
144
|
# Queries whether the current environment is a valid FaaS environment.
|
|
119
145
|
#
|
|
120
146
|
# @return [ true | false ] whether the environment is a FaaS
|
|
@@ -159,14 +185,11 @@ module Mongo
|
|
|
159
185
|
@name == 'vercel'
|
|
160
186
|
end
|
|
161
187
|
|
|
162
|
-
# Compiles the detected environment information into a Hash.
|
|
163
|
-
# always include a {{name}} key, but may include other keys as well,
|
|
164
|
-
# depending on the detected FaaS environment. (See the handshake
|
|
165
|
-
# spec for details.)
|
|
188
|
+
# Compiles the detected environment information into a Hash.
|
|
166
189
|
#
|
|
167
190
|
# @return [ Hash ] the detected environment information.
|
|
168
191
|
def to_h
|
|
169
|
-
fields.merge(name: name)
|
|
192
|
+
name ? fields.merge(name: name) : fields
|
|
170
193
|
end
|
|
171
194
|
|
|
172
195
|
private
|
|
@@ -192,6 +215,38 @@ module Mongo
|
|
|
192
215
|
names.first
|
|
193
216
|
end
|
|
194
217
|
|
|
218
|
+
# Looks for the presence of a container. Currently can detect
|
|
219
|
+
# Docker (by the existence of a .dockerenv file in the root
|
|
220
|
+
# directory) and Kubernetes (by the existence of the KUBERNETES_SERVICE_HOST
|
|
221
|
+
# environment variable).
|
|
222
|
+
def detect_container
|
|
223
|
+
runtime = docker_present? && 'docker'
|
|
224
|
+
orchestrator = kubernetes_present? && 'kubernetes'
|
|
225
|
+
|
|
226
|
+
return unless runtime || orchestrator
|
|
227
|
+
|
|
228
|
+
fields[:container] = {}
|
|
229
|
+
fields[:container][:runtime] = runtime if runtime
|
|
230
|
+
fields[:container][:orchestrator] = orchestrator if orchestrator
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Checks for the existence of a .dockerenv in the root directory.
|
|
234
|
+
def docker_present?
|
|
235
|
+
File.exist?(dockerenv_path)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Implementing this as a method so that it can be mocked in tests, to
|
|
239
|
+
# test the presence or absence of Docker.
|
|
240
|
+
def dockerenv_path
|
|
241
|
+
DOCKERENV_PATH
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Checks for the presence of a non-empty KUBERNETES_SERVICE_HOST
|
|
245
|
+
# environment variable.
|
|
246
|
+
def kubernetes_present?
|
|
247
|
+
!ENV['KUBERNETES_SERVICE_HOST'].to_s.empty?
|
|
248
|
+
end
|
|
249
|
+
|
|
195
250
|
# Determines whether the named environment variable exists, and (if
|
|
196
251
|
# a pattern has been declared for that descriminator) whether the
|
|
197
252
|
# pattern matches the value of the variable.
|
|
@@ -212,10 +267,10 @@ module Mongo
|
|
|
212
267
|
# Extracts environment information from the current environment
|
|
213
268
|
# variables, based on the detected FaaS environment. Populates the
|
|
214
269
|
# {{@fields}} instance variable.
|
|
215
|
-
def
|
|
270
|
+
def populate_faas_fields
|
|
216
271
|
return unless name
|
|
217
272
|
|
|
218
|
-
|
|
273
|
+
FIELDS[name].each_with_object(@fields) do |(var, defn), fields|
|
|
219
274
|
fields[defn[:field]] = extract_field(var, defn)
|
|
220
275
|
end
|
|
221
276
|
end
|
|
@@ -187,13 +187,14 @@ module Mongo
|
|
|
187
187
|
}
|
|
188
188
|
end
|
|
189
189
|
|
|
190
|
-
# Returns the environment doc describing the current
|
|
190
|
+
# Returns the environment doc describing the current execution
|
|
191
|
+
# environment.
|
|
191
192
|
#
|
|
192
|
-
# @return [ Hash | nil ] the environment doc (or nil if
|
|
193
|
-
#
|
|
193
|
+
# @return [ Hash | nil ] the environment doc (or nil if no relevant
|
|
194
|
+
# environment info was detected)
|
|
194
195
|
def env_doc
|
|
195
196
|
env = Environment.new
|
|
196
|
-
env.
|
|
197
|
+
env.present? ? env.to_h : nil
|
|
197
198
|
end
|
|
198
199
|
|
|
199
200
|
def type
|
|
@@ -48,6 +48,7 @@ module Mongo
|
|
|
48
48
|
# provided by the client during findAndModify operations, requiring the
|
|
49
49
|
# driver to raise client-side errors when those options are provided.
|
|
50
50
|
find_and_modify_option_validation: 8,
|
|
51
|
+
sharded_transactions: 8,
|
|
51
52
|
transactions: 7,
|
|
52
53
|
scram_sha_256: 7,
|
|
53
54
|
array_filters: 6,
|
|
@@ -164,6 +164,10 @@ module Mongo
|
|
|
164
164
|
# for mongos pinning. Added in version 2.10.0.
|
|
165
165
|
# @param [ true | false ] write_aggregation Whether we need a server that
|
|
166
166
|
# supports writing aggregations (e.g. with $merge/$out) on secondaries.
|
|
167
|
+
# @param [ Array<Server> ] deprioritized A list of servers that should
|
|
168
|
+
# be selected from only if no other servers are available. This is
|
|
169
|
+
# used to avoid selecting the same server twice in a row when
|
|
170
|
+
# retrying a command.
|
|
167
171
|
#
|
|
168
172
|
# @return [ Mongo::Server ] A server matching the server preference.
|
|
169
173
|
#
|
|
@@ -174,8 +178,8 @@ module Mongo
|
|
|
174
178
|
# lint mode is enabled.
|
|
175
179
|
#
|
|
176
180
|
# @since 2.0.0
|
|
177
|
-
def select_server(cluster, ping = nil, session = nil, write_aggregation: false)
|
|
178
|
-
select_server_impl(cluster, ping, session, write_aggregation).tap do |server|
|
|
181
|
+
def select_server(cluster, ping = nil, session = nil, write_aggregation: false, deprioritized: [])
|
|
182
|
+
select_server_impl(cluster, ping, session, write_aggregation, deprioritized).tap do |server|
|
|
179
183
|
if Lint.enabled? && !server.pool.ready?
|
|
180
184
|
raise Error::LintError, 'Server selector returning a server with a pool which is not ready'
|
|
181
185
|
end
|
|
@@ -183,7 +187,7 @@ module Mongo
|
|
|
183
187
|
end
|
|
184
188
|
|
|
185
189
|
# Parameters and return values are the same as for select_server.
|
|
186
|
-
private def select_server_impl(cluster, ping, session, write_aggregation)
|
|
190
|
+
private def select_server_impl(cluster, ping, session, write_aggregation, deprioritized)
|
|
187
191
|
if cluster.topology.is_a?(Cluster::Topology::LoadBalanced)
|
|
188
192
|
return cluster.servers.first
|
|
189
193
|
end
|
|
@@ -266,7 +270,7 @@ module Mongo
|
|
|
266
270
|
end
|
|
267
271
|
end
|
|
268
272
|
|
|
269
|
-
server = try_select_server(cluster, write_aggregation: write_aggregation)
|
|
273
|
+
server = try_select_server(cluster, write_aggregation: write_aggregation, deprioritized: deprioritized)
|
|
270
274
|
|
|
271
275
|
if server
|
|
272
276
|
unless cluster.topology.compatible?
|
|
@@ -321,11 +325,15 @@ module Mongo
|
|
|
321
325
|
# an eligible server.
|
|
322
326
|
# @param [ true | false ] write_aggregation Whether we need a server that
|
|
323
327
|
# supports writing aggregations (e.g. with $merge/$out) on secondaries.
|
|
328
|
+
# @param [ Array<Server> ] deprioritized A list of servers that should
|
|
329
|
+
# be selected from only if no other servers are available. This is
|
|
330
|
+
# used to avoid selecting the same server twice in a row when
|
|
331
|
+
# retrying a command.
|
|
324
332
|
#
|
|
325
333
|
# @return [ Server | nil ] A suitable server, if one exists.
|
|
326
334
|
#
|
|
327
335
|
# @api private
|
|
328
|
-
def try_select_server(cluster, write_aggregation: false)
|
|
336
|
+
def try_select_server(cluster, write_aggregation: false, deprioritized: [])
|
|
329
337
|
servers = if write_aggregation && cluster.replica_set?
|
|
330
338
|
# 1. Check if ALL servers in cluster support secondary writes.
|
|
331
339
|
is_write_supported = cluster.servers.reduce(true) do |res, server|
|
|
@@ -347,7 +355,7 @@ module Mongo
|
|
|
347
355
|
# by the selector (e.g. for secondary preferred, the first
|
|
348
356
|
# server may be a secondary and the second server may be primary)
|
|
349
357
|
# and we should take the first server here respecting the order
|
|
350
|
-
server = servers
|
|
358
|
+
server = suitable_server(servers, deprioritized)
|
|
351
359
|
|
|
352
360
|
if server
|
|
353
361
|
if Lint.enabled?
|
|
@@ -418,6 +426,24 @@ module Mongo
|
|
|
418
426
|
|
|
419
427
|
private
|
|
420
428
|
|
|
429
|
+
# Returns a server from the list of servers that is suitable for
|
|
430
|
+
# executing the operation.
|
|
431
|
+
#
|
|
432
|
+
# @param [ Array<Server> ] servers The candidate servers.
|
|
433
|
+
# @param [ Array<Server> ] deprioritized A list of servers that should
|
|
434
|
+
# be selected from only if no other servers are available.
|
|
435
|
+
#
|
|
436
|
+
# @return [ Server | nil ] The suitable server or nil if no suitable
|
|
437
|
+
# server is available.
|
|
438
|
+
def suitable_server(servers, deprioritized)
|
|
439
|
+
preferred = servers - deprioritized
|
|
440
|
+
if preferred.empty?
|
|
441
|
+
servers.first
|
|
442
|
+
else
|
|
443
|
+
preferred.first
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
421
447
|
# Convert this server preference definition into a format appropriate
|
|
422
448
|
# for sending to a MongoDB server (i.e., as a command field).
|
|
423
449
|
#
|