mongo 2.20.1 → 2.21.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -0
- data/Rakefile +2 -2
- data/lib/mongo/address.rb +22 -3
- data/lib/mongo/auth/aws/credentials_retriever.rb +70 -17
- data/lib/mongo/auth/base.rb +1 -1
- data/lib/mongo/bulk_write.rb +35 -2
- data/lib/mongo/client.rb +38 -6
- data/lib/mongo/client_encryption.rb +6 -3
- data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -1
- data/lib/mongo/cluster/sdam_flow.rb +20 -7
- data/lib/mongo/cluster.rb +14 -4
- data/lib/mongo/collection/helpers.rb +1 -1
- data/lib/mongo/collection/view/aggregation/behavior.rb +131 -0
- data/lib/mongo/collection/view/aggregation.rb +33 -99
- data/lib/mongo/collection/view/builder/aggregation.rb +1 -7
- data/lib/mongo/collection/view/change_stream.rb +80 -27
- data/lib/mongo/collection/view/iterable.rb +76 -60
- data/lib/mongo/collection/view/map_reduce.rb +25 -8
- data/lib/mongo/collection/view/readable.rb +79 -30
- data/lib/mongo/collection/view/writable.rb +109 -48
- data/lib/mongo/collection/view.rb +43 -3
- data/lib/mongo/collection.rb +158 -23
- data/lib/mongo/crypt/auto_encrypter.rb +4 -6
- data/lib/mongo/crypt/binding.rb +4 -4
- data/lib/mongo/crypt/context.rb +20 -14
- data/lib/mongo/crypt/encryption_io.rb +56 -26
- data/lib/mongo/crypt/explicit_encrypter.rb +49 -20
- data/lib/mongo/crypt/explicit_encryption_context.rb +17 -11
- data/lib/mongo/crypt/kms/azure/credentials_retriever.rb +22 -6
- data/lib/mongo/crypt/kms/gcp/credentials_retriever.rb +29 -4
- data/lib/mongo/csot_timeout_holder.rb +119 -0
- data/lib/mongo/cursor/kill_spec.rb +5 -2
- data/lib/mongo/cursor/nontailable.rb +27 -0
- data/lib/mongo/cursor.rb +86 -24
- data/lib/mongo/cursor_host.rb +82 -0
- data/lib/mongo/database/view.rb +81 -14
- data/lib/mongo/database.rb +88 -18
- data/lib/mongo/error/operation_failure.rb +209 -204
- data/lib/mongo/error/server_timeout_error.rb +12 -0
- data/lib/mongo/error/socket_timeout_error.rb +3 -1
- data/lib/mongo/error/timeout_error.rb +23 -0
- data/lib/mongo/error.rb +2 -0
- data/lib/mongo/grid/fs_bucket.rb +45 -12
- data/lib/mongo/grid/stream/read.rb +15 -1
- data/lib/mongo/grid/stream/write.rb +21 -4
- data/lib/mongo/index/view.rb +77 -16
- data/lib/mongo/operation/context.rb +40 -2
- data/lib/mongo/operation/create_search_indexes/op_msg.rb +2 -2
- data/lib/mongo/operation/delete/op_msg.rb +2 -1
- data/lib/mongo/operation/drop_search_index/op_msg.rb +2 -2
- data/lib/mongo/operation/find/op_msg.rb +45 -0
- data/lib/mongo/operation/get_more/op_msg.rb +33 -0
- data/lib/mongo/operation/insert/op_msg.rb +3 -2
- data/lib/mongo/operation/insert/result.rb +4 -2
- data/lib/mongo/operation/list_collections/result.rb +1 -1
- data/lib/mongo/operation/map_reduce/result.rb +1 -1
- data/lib/mongo/operation/op_msg_base.rb +3 -1
- data/lib/mongo/operation/result.rb +26 -5
- data/lib/mongo/operation/shared/executable.rb +12 -1
- data/lib/mongo/operation/shared/op_msg_executable.rb +4 -1
- data/lib/mongo/operation/shared/response_handling.rb +3 -3
- data/lib/mongo/operation/shared/sessions_supported.rb +1 -1
- data/lib/mongo/operation/shared/timed.rb +52 -0
- data/lib/mongo/operation/shared/write.rb +4 -1
- data/lib/mongo/operation/update/op_msg.rb +2 -1
- data/lib/mongo/operation/update_search_index/op_msg.rb +2 -2
- data/lib/mongo/operation.rb +1 -0
- data/lib/mongo/protocol/message.rb +1 -4
- data/lib/mongo/protocol/msg.rb +2 -2
- data/lib/mongo/retryable/read_worker.rb +69 -29
- data/lib/mongo/retryable/write_worker.rb +49 -18
- data/lib/mongo/retryable.rb +8 -2
- data/lib/mongo/server/connection.rb +11 -5
- data/lib/mongo/server/connection_base.rb +22 -2
- data/lib/mongo/server/connection_pool.rb +32 -14
- data/lib/mongo/server/description/features.rb +1 -1
- data/lib/mongo/server/description.rb +18 -5
- data/lib/mongo/server/monitor.rb +7 -4
- data/lib/mongo/server/pending_connection.rb +7 -3
- data/lib/mongo/server/{round_trip_time_averager.rb → round_trip_time_calculator.rb} +25 -7
- data/lib/mongo/server.rb +11 -6
- data/lib/mongo/server_selector/base.rb +25 -9
- data/lib/mongo/session.rb +78 -9
- data/lib/mongo/socket/ssl.rb +109 -17
- data/lib/mongo/socket/tcp.rb +40 -6
- data/lib/mongo/socket.rb +154 -25
- data/lib/mongo/uri/options_mapper.rb +1 -0
- data/lib/mongo/version.rb +1 -1
- data/lib/mongo.rb +1 -0
- data/spec/atlas/atlas_connectivity_spec.rb +4 -0
- data/spec/atlas/operations_spec.rb +4 -0
- data/spec/integration/client_side_encryption/auto_encryption_mongocryptd_spawn_spec.rb +2 -1
- data/spec/integration/client_side_encryption/auto_encryption_spec.rb +494 -487
- data/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb +1 -1
- data/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb +66 -22
- data/spec/integration/client_side_operations_timeout/encryption_prose_spec.rb +131 -0
- data/spec/integration/connection_pool_populator_spec.rb +2 -0
- data/spec/integration/cursor_pinning_spec.rb +15 -60
- data/spec/integration/cursor_reaping_spec.rb +1 -1
- data/spec/integration/docs_examples_spec.rb +1 -1
- data/spec/integration/operation_failure_code_spec.rb +1 -1
- data/spec/integration/operation_failure_message_spec.rb +3 -3
- data/spec/integration/retryable_errors_spec.rb +2 -2
- data/spec/integration/sdam_error_handling_spec.rb +2 -1
- data/spec/integration/search_indexes_prose_spec.rb +4 -0
- data/spec/integration/server_spec.rb +4 -3
- data/spec/integration/transactions_api_examples_spec.rb +2 -0
- data/spec/kerberos/kerberos_spec.rb +4 -0
- data/spec/lite_spec_helper.rb +3 -1
- data/spec/mongo/auth/user/view_spec.rb +1 -1
- data/spec/mongo/caching_cursor_spec.rb +1 -1
- data/spec/mongo/client_encryption_spec.rb +1 -0
- data/spec/mongo/client_spec.rb +158 -4
- data/spec/mongo/collection/view/aggregation_spec.rb +14 -39
- data/spec/mongo/collection/view/change_stream_spec.rb +3 -3
- data/spec/mongo/collection_spec.rb +5 -6
- data/spec/mongo/crypt/auto_encrypter_spec.rb +14 -12
- data/spec/mongo/crypt/data_key_context_spec.rb +3 -1
- data/spec/mongo/crypt/explicit_encryption_context_spec.rb +2 -2
- data/spec/mongo/crypt/handle_spec.rb +1 -1
- data/spec/mongo/cursor_spec.rb +26 -9
- data/spec/mongo/error/operation_failure_heavy_spec.rb +2 -2
- data/spec/mongo/operation/context_spec.rb +79 -0
- data/spec/mongo/operation/create/op_msg_spec.rb +106 -110
- data/spec/mongo/operation/delete/op_msg_spec.rb +6 -5
- data/spec/mongo/operation/find/op_msg_spec.rb +66 -0
- data/spec/mongo/operation/get_more/op_msg_spec.rb +65 -0
- data/spec/mongo/operation/insert/op_msg_spec.rb +128 -131
- data/spec/mongo/operation/shared/csot/examples.rb +113 -0
- data/spec/mongo/query_cache_spec.rb +243 -225
- data/spec/mongo/retryable_spec.rb +1 -0
- data/spec/mongo/server/round_trip_time_calculator_spec.rb +120 -0
- data/spec/mongo/socket/ssl_spec.rb +0 -10
- data/spec/runners/change_streams/test.rb +2 -2
- data/spec/runners/crud/operation.rb +1 -1
- data/spec/runners/crud/verifier.rb +3 -1
- data/spec/runners/transactions/operation.rb +4 -6
- data/spec/runners/unified/ambiguous_operations.rb +13 -0
- data/spec/runners/unified/assertions.rb +4 -0
- data/spec/runners/unified/change_stream_operations.rb +14 -24
- data/spec/runners/unified/crud_operations.rb +82 -59
- data/spec/runners/unified/ddl_operations.rb +38 -7
- data/spec/runners/unified/grid_fs_operations.rb +37 -2
- data/spec/runners/unified/support_operations.rb +43 -4
- data/spec/runners/unified/test.rb +22 -10
- data/spec/runners/unified.rb +1 -1
- data/spec/solo/clean_exit_spec.rb +2 -0
- data/spec/spec_tests/client_side_operations_timeout_spec.rb +15 -0
- data/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml +3 -1
- data/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml +3 -1
- data/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml +3 -1
- data/spec/spec_tests/data/change_streams_unified/change-streams-pre_and_post_images.yml +1 -1
- data/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml +1 -1
- data/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml +1 -1
- data/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml +1 -1
- data/spec/spec_tests/data/client_side_encryption/badQueries.yml +2 -1
- data/spec/spec_tests/data/client_side_encryption/timeoutMS.yml +67 -0
- data/spec/spec_tests/data/client_side_operations_timeout/bulkWrite.yml +87 -0
- data/spec/spec_tests/data/client_side_operations_timeout/change-streams.yml +358 -0
- data/spec/spec_tests/data/client_side_operations_timeout/close-cursors.yml +129 -0
- data/spec/spec_tests/data/client_side_operations_timeout/command-execution.yml +250 -0
- data/spec/spec_tests/data/client_side_operations_timeout/convenient-transactions.yml +113 -0
- data/spec/spec_tests/data/client_side_operations_timeout/cursors.yml +70 -0
- data/spec/spec_tests/data/client_side_operations_timeout/deprecated-options.yml +3982 -0
- data/spec/spec_tests/data/client_side_operations_timeout/error-transformations.yml +96 -0
- data/spec/spec_tests/data/client_side_operations_timeout/global-timeoutMS.yml +3236 -0
- data/spec/spec_tests/data/client_side_operations_timeout/gridfs-advanced.yml +207 -0
- data/spec/spec_tests/data/client_side_operations_timeout/gridfs-delete.yml +152 -0
- data/spec/spec_tests/data/client_side_operations_timeout/gridfs-download.yml +182 -0
- data/spec/spec_tests/data/client_side_operations_timeout/gridfs-find.yml +100 -0
- data/spec/spec_tests/data/client_side_operations_timeout/gridfs-upload.yml +249 -0
- data/spec/spec_tests/data/client_side_operations_timeout/legacy-timeouts.yml +204 -0
- data/spec/spec_tests/data/client_side_operations_timeout/non-tailable-cursors.yml +307 -0
- data/spec/spec_tests/data/client_side_operations_timeout/override-collection-timeoutMS.yml +1877 -0
- data/spec/spec_tests/data/client_side_operations_timeout/override-operation-timeoutMS.yml +1918 -0
- data/spec/spec_tests/data/client_side_operations_timeout/retryability-legacy-timeouts.yml +1676 -0
- data/spec/spec_tests/data/client_side_operations_timeout/retryability-timeoutMS.yml +2824 -0
- data/spec/spec_tests/data/client_side_operations_timeout/sessions-inherit-timeoutMS.yml +168 -0
- data/spec/spec_tests/data/client_side_operations_timeout/sessions-override-operation-timeoutMS.yml +171 -0
- data/spec/spec_tests/data/client_side_operations_timeout/sessions-override-timeoutMS.yml +168 -0
- data/spec/spec_tests/data/client_side_operations_timeout/tailable-awaitData.yml +247 -0
- data/spec/spec_tests/data/client_side_operations_timeout/tailable-non-awaitData.yml +181 -0
- data/spec/spec_tests/data/crud_unified/aggregate-write-readPreference.yml +4 -0
- data/spec/spec_tests/data/crud_unified/db-aggregate-write-readPreference.yml +4 -0
- data/spec/spec_tests/data/crud_unified/find-test-all-options.yml +29 -0
- data/spec/spec_tests/server_selection_rtt_spec.rb +6 -6
- data/spec/support/certificates/atlas-ocsp-ca.crt +81 -83
- data/spec/support/certificates/atlas-ocsp.crt +107 -107
- data/spec/support/cluster_tools.rb +3 -3
- data/spec/support/common_shortcuts.rb +2 -2
- data/spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json +1 -1
- data/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json +1 -1
- data/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json +1 -1
- data/spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json +1 -1
- data/spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json +1 -1
- data/spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json +1 -1
- data/spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json +1 -1
- data/spec/support/shared/session.rb +2 -2
- data/spec/support/spec_setup.rb +2 -2
- data/spec/support/utils.rb +3 -1
- metadata +78 -91
- data/spec/mongo/server/round_trip_time_averager_spec.rb +0 -48
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml +0 -242
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml +0 -423
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml +0 -183
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml +0 -240
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml +0 -236
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml +0 -253
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml +0 -1688
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml +0 -294
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml +0 -906
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml +0 -1685
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml +0 -1681
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml +0 -1698
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml +0 -330
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml +0 -425
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml +0 -227
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml +0 -328
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml +0 -320
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml +0 -337
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml +0 -914
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml +0 -293
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml +0 -519
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml +0 -912
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml +0 -908
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml +0 -925
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml +0 -326
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml +0 -425
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml +0 -225
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml +0 -324
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml +0 -320
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml +0 -339
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml +0 -242
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml +0 -424
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml +0 -183
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml +0 -240
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml +0 -236
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml +0 -255
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml +0 -242
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml +0 -423
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml +0 -183
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml +0 -240
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml +0 -236
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml +0 -255
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml +0 -44
@@ -83,6 +83,12 @@ module Mongo
|
|
83
83
|
@options.freeze
|
84
84
|
@filename = @options[:filename]
|
85
85
|
@open = true
|
86
|
+
@timeout_holder = CsotTimeoutHolder.new(
|
87
|
+
operation_timeouts: {
|
88
|
+
operation_timeout_ms: options[:timeout_ms],
|
89
|
+
inherited_timeout_ms: fs.database.timeout_ms
|
90
|
+
}
|
91
|
+
)
|
86
92
|
end
|
87
93
|
|
88
94
|
# Write to the GridFS bucket from the source stream or a string.
|
@@ -107,7 +113,12 @@ module Mongo
|
|
107
113
|
end
|
108
114
|
chunks = File::Chunk.split(io, file_info, @n)
|
109
115
|
@n += chunks.size
|
110
|
-
|
116
|
+
unless chunks.empty?
|
117
|
+
chunks_collection.insert_many(
|
118
|
+
chunks,
|
119
|
+
timeout_ms: @timeout_holder.remaining_timeout_ms!
|
120
|
+
)
|
121
|
+
end
|
111
122
|
self
|
112
123
|
end
|
113
124
|
|
@@ -124,7 +135,10 @@ module Mongo
|
|
124
135
|
def close
|
125
136
|
ensure_open!
|
126
137
|
update_length
|
127
|
-
files_collection.insert_one(
|
138
|
+
files_collection.insert_one(
|
139
|
+
file_info,
|
140
|
+
@options.merge(timeout_ms: @timeout_holder.remaining_timeout_ms!)
|
141
|
+
)
|
128
142
|
@open = false
|
129
143
|
file_id
|
130
144
|
end
|
@@ -166,7 +180,10 @@ module Mongo
|
|
166
180
|
#
|
167
181
|
# @since 2.1.0
|
168
182
|
def abort
|
169
|
-
fs.chunks_collection.find(
|
183
|
+
fs.chunks_collection.find(
|
184
|
+
{ :files_id => file_id },
|
185
|
+
@options.merge(timeout_ms: @timeout_holder.remaining_timeout_ms!)
|
186
|
+
).delete_many
|
170
187
|
(@open = false) || true
|
171
188
|
end
|
172
189
|
|
@@ -200,7 +217,7 @@ module Mongo
|
|
200
217
|
end
|
201
218
|
|
202
219
|
def ensure_indexes!
|
203
|
-
fs.send(:ensure_indexes
|
220
|
+
fs.send(:ensure_indexes!, @timeout_holder)
|
204
221
|
end
|
205
222
|
|
206
223
|
def ensure_open!
|
data/lib/mongo/index/view.rb
CHANGED
@@ -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/cursor/nontailable'
|
19
|
+
|
18
20
|
module Mongo
|
19
21
|
module Index
|
20
22
|
|
@@ -25,6 +27,8 @@ module Mongo
|
|
25
27
|
extend Forwardable
|
26
28
|
include Enumerable
|
27
29
|
include Retryable
|
30
|
+
include Mongo::CursorHost
|
31
|
+
include Cursor::NonTailable
|
28
32
|
|
29
33
|
# @return [ Collection ] collection The indexes collection.
|
30
34
|
attr_reader :collection
|
@@ -33,6 +37,12 @@ module Mongo
|
|
33
37
|
# when sending the listIndexes command.
|
34
38
|
attr_reader :batch_size
|
35
39
|
|
40
|
+
# @return [ Integer | nil | The timeout_ms value that was passed as an
|
41
|
+
# option to the view.
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
attr_reader :operation_timeout_ms
|
45
|
+
|
36
46
|
def_delegators :@collection, :cluster, :database, :read_preference, :write_concern, :client
|
37
47
|
def_delegators :cluster, :next_primary
|
38
48
|
|
@@ -90,7 +100,7 @@ module Mongo
|
|
90
100
|
# @since 2.0.0
|
91
101
|
def drop_one(name, options = {})
|
92
102
|
raise Error::MultiIndexDrop.new if name == Index::ALL
|
93
|
-
drop_by_name(name,
|
103
|
+
drop_by_name(name, options)
|
94
104
|
end
|
95
105
|
|
96
106
|
# Drop all indexes on the collection.
|
@@ -107,7 +117,7 @@ module Mongo
|
|
107
117
|
#
|
108
118
|
# @since 2.0.0
|
109
119
|
def drop_all(options = {})
|
110
|
-
drop_by_name(Index::ALL,
|
120
|
+
drop_by_name(Index::ALL, options)
|
111
121
|
end
|
112
122
|
|
113
123
|
# Creates an index on the collection.
|
@@ -161,7 +171,7 @@ module Mongo
|
|
161
171
|
if session = @options[:session]
|
162
172
|
create_options[:session] = session
|
163
173
|
end
|
164
|
-
%i(commit_quorum session comment).each do |key|
|
174
|
+
%i(commit_quorum session comment timeout_ms max_time_ms).each do |key|
|
165
175
|
if value = options.delete(key)
|
166
176
|
create_options[key] = value
|
167
177
|
end
|
@@ -210,7 +220,7 @@ module Mongo
|
|
210
220
|
options = models.pop
|
211
221
|
end
|
212
222
|
|
213
|
-
client.
|
223
|
+
client.with_session(@options.merge(options)) do |session|
|
214
224
|
server = next_primary(nil, session)
|
215
225
|
|
216
226
|
indexes = normalize_models(models, server)
|
@@ -229,8 +239,12 @@ module Mongo
|
|
229
239
|
write_concern: write_concern,
|
230
240
|
comment: options[:comment],
|
231
241
|
}
|
232
|
-
|
233
|
-
|
242
|
+
context = Operation::Context.new(
|
243
|
+
client: client,
|
244
|
+
session: session,
|
245
|
+
operation_timeouts: operation_timeouts(options)
|
246
|
+
)
|
247
|
+
Operation::CreateIndex.new(spec).execute(server, context: context)
|
234
248
|
end
|
235
249
|
end
|
236
250
|
|
@@ -263,9 +277,15 @@ module Mongo
|
|
263
277
|
#
|
264
278
|
# @since 2.0.0
|
265
279
|
def each(&block)
|
266
|
-
session = client.
|
267
|
-
|
268
|
-
|
280
|
+
session = client.get_session(@options)
|
281
|
+
context = Operation::Context.new(
|
282
|
+
client: client,
|
283
|
+
session: session,
|
284
|
+
operation_timeouts: operation_timeouts(@options)
|
285
|
+
)
|
286
|
+
|
287
|
+
cursor = read_with_retry_cursor(session, ServerSelector.primary, self, context: context) do |server|
|
288
|
+
send_initial_query(server, session, context)
|
269
289
|
end
|
270
290
|
if block_given?
|
271
291
|
cursor.each do |doc|
|
@@ -283,22 +303,53 @@ module Mongo
|
|
283
303
|
#
|
284
304
|
# @param [ Collection ] collection The collection.
|
285
305
|
# @param [ Hash ] options Options for getting a list of indexes.
|
286
|
-
# Only relevant for when the listIndexes command is used with server
|
287
|
-
# versions >=2.8.
|
288
306
|
#
|
289
307
|
# @option options [ Integer ] :batch_size The batch size for results
|
290
308
|
# returned from the listIndexes command.
|
309
|
+
# @option options [ :cursor_lifetime | :iteration ] :timeout_mode How to interpret
|
310
|
+
# :timeout_ms (whether it applies to the lifetime of the cursor, or per
|
311
|
+
# iteration).
|
312
|
+
# @option options [ Integer ] :timeout_ms The operation timeout in milliseconds.
|
313
|
+
# Must be a non-negative integer. An explicit value of 0 means infinite.
|
314
|
+
# The default value is unset which means the value is inherited from
|
315
|
+
# the collection or the database or the client.
|
291
316
|
#
|
292
317
|
# @since 2.0.0
|
293
318
|
def initialize(collection, options = {})
|
294
319
|
@collection = collection
|
320
|
+
@operation_timeout_ms = options.delete(:timeout_ms)
|
321
|
+
|
322
|
+
validate_timeout_mode!(options)
|
323
|
+
|
295
324
|
@batch_size = options[:batch_size]
|
296
325
|
@options = options
|
297
326
|
end
|
298
327
|
|
328
|
+
# The timeout_ms value to use for this operation; either specified as an
|
329
|
+
# option to the view, or inherited from the collection.
|
330
|
+
#
|
331
|
+
# @return [ Integer | nil ] the timeout_ms for this operation
|
332
|
+
def timeout_ms
|
333
|
+
operation_timeout_ms || collection.timeout_ms
|
334
|
+
end
|
335
|
+
|
336
|
+
# @return [ Hash ] timeout_ms value set on the operation level (if any),
|
337
|
+
# and/or timeout_ms that is set on collection/database/client level (if any).
|
338
|
+
#
|
339
|
+
# @api private
|
340
|
+
def operation_timeouts(opts = {})
|
341
|
+
{}.tap do |result|
|
342
|
+
if opts[:timeout_ms] || operation_timeout_ms
|
343
|
+
result[:operation_timeout_ms] = opts.delete(:timeout_ms) || operation_timeout_ms
|
344
|
+
else
|
345
|
+
result[:inherited_timeout_ms] = collection.timeout_ms
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
299
350
|
private
|
300
351
|
|
301
|
-
def drop_by_name(name,
|
352
|
+
def drop_by_name(name, opts = {})
|
302
353
|
client.send(:with_session, @options) do |session|
|
303
354
|
spec = {
|
304
355
|
db_name: database.name,
|
@@ -307,9 +358,14 @@ module Mongo
|
|
307
358
|
session: session,
|
308
359
|
write_concern: write_concern,
|
309
360
|
}
|
310
|
-
spec[:comment] = comment unless comment.nil?
|
361
|
+
spec[:comment] = opts[:comment] unless opts[:comment].nil?
|
311
362
|
server = next_primary(nil, session)
|
312
|
-
|
363
|
+
context = Operation::Context.new(
|
364
|
+
client: client,
|
365
|
+
session: session,
|
366
|
+
operation_timeouts: operation_timeouts(opts)
|
367
|
+
)
|
368
|
+
Operation::DropIndex.new(spec).execute(server, context: context)
|
313
369
|
end
|
314
370
|
end
|
315
371
|
|
@@ -347,8 +403,13 @@ module Mongo
|
|
347
403
|
end
|
348
404
|
end
|
349
405
|
|
350
|
-
def send_initial_query(server, session)
|
351
|
-
|
406
|
+
def send_initial_query(server, session, context)
|
407
|
+
if server.load_balancer?
|
408
|
+
connection = server.pool.check_out(context: context)
|
409
|
+
initial_query_op(session).execute_with_connection(connection, context: context)
|
410
|
+
else
|
411
|
+
initial_query_op(session).execute(server, context: context)
|
412
|
+
end
|
352
413
|
end
|
353
414
|
end
|
354
415
|
end
|
@@ -34,8 +34,15 @@ module Mongo
|
|
34
34
|
# operations.
|
35
35
|
#
|
36
36
|
# @api private
|
37
|
-
class Context
|
38
|
-
def initialize(
|
37
|
+
class Context < CsotTimeoutHolder
|
38
|
+
def initialize(
|
39
|
+
client: nil,
|
40
|
+
session: nil,
|
41
|
+
connection_global_id: nil,
|
42
|
+
operation_timeouts: {},
|
43
|
+
view: nil,
|
44
|
+
options: nil
|
45
|
+
)
|
39
46
|
if options
|
40
47
|
if client
|
41
48
|
raise ArgumentError, 'Client and options cannot both be specified'
|
@@ -52,14 +59,33 @@ module Mongo
|
|
52
59
|
|
53
60
|
@client = client
|
54
61
|
@session = session
|
62
|
+
@view = view
|
55
63
|
@connection_global_id = connection_global_id
|
56
64
|
@options = options
|
65
|
+
super(session: session, operation_timeouts: operation_timeouts)
|
57
66
|
end
|
58
67
|
|
59
68
|
attr_reader :client
|
60
69
|
attr_reader :session
|
70
|
+
attr_reader :view
|
61
71
|
attr_reader :options
|
62
72
|
|
73
|
+
# Returns a new Operation::Context with the deadline refreshed
|
74
|
+
# and relative to the current moment.
|
75
|
+
#
|
76
|
+
# @return [ Operation::Context ] the refreshed context
|
77
|
+
def refresh(connection_global_id: @connection_global_id, timeout_ms: nil, view: nil)
|
78
|
+
operation_timeouts = @operation_timeouts
|
79
|
+
operation_timeouts = operation_timeouts.merge(operation_timeout_ms: timeout_ms) if timeout_ms
|
80
|
+
|
81
|
+
self.class.new(client: client,
|
82
|
+
session: session,
|
83
|
+
connection_global_id: connection_global_id,
|
84
|
+
operation_timeouts: operation_timeouts,
|
85
|
+
view: view || self.view,
|
86
|
+
options: options)
|
87
|
+
end
|
88
|
+
|
63
89
|
def connection_global_id
|
64
90
|
@connection_global_id || session&.pinned_connection_global_id
|
65
91
|
end
|
@@ -122,10 +148,18 @@ module Mongo
|
|
122
148
|
client&.encrypter&.encrypt? || false
|
123
149
|
end
|
124
150
|
|
151
|
+
def encrypt(db_name, cmd)
|
152
|
+
encrypter.encrypt(db_name, cmd, self)
|
153
|
+
end
|
154
|
+
|
125
155
|
def decrypt?
|
126
156
|
!!client&.encrypter
|
127
157
|
end
|
128
158
|
|
159
|
+
def decrypt(cmd)
|
160
|
+
encrypter.decrypt(cmd, self)
|
161
|
+
end
|
162
|
+
|
129
163
|
def encrypter
|
130
164
|
if client&.encrypter
|
131
165
|
client.encrypter
|
@@ -133,6 +167,10 @@ module Mongo
|
|
133
167
|
raise Error::InternalDriverError, 'Encrypter should only be accessed when encryption is to be performed'
|
134
168
|
end
|
135
169
|
end
|
170
|
+
|
171
|
+
def inspect
|
172
|
+
"#<#{self.class} connection_global_id=#{connection_global_id.inspect} deadline=#{deadline.inspect} options=#{options.inspect} operation_timeouts=#{operation_timeouts.inspect}>"
|
173
|
+
end
|
136
174
|
end
|
137
175
|
end
|
138
176
|
end
|
@@ -14,11 +14,11 @@ module Mongo
|
|
14
14
|
# Returns the command to send to the database, describing the
|
15
15
|
# desired createSearchIndexes operation.
|
16
16
|
#
|
17
|
-
# @param [
|
17
|
+
# @param [ Connection ] _connection the connection that will receive the
|
18
18
|
# command
|
19
19
|
#
|
20
20
|
# @return [ Hash ] the selector
|
21
|
-
def selector(
|
21
|
+
def selector(_connection)
|
22
22
|
{
|
23
23
|
createSearchIndexes: coll_name,
|
24
24
|
:$db => db_name,
|
@@ -49,7 +49,8 @@ module Mongo
|
|
49
49
|
|
50
50
|
def message(connection)
|
51
51
|
section = Protocol::Msg::Section1.new(IDENTIFIER, send(IDENTIFIER))
|
52
|
-
|
52
|
+
cmd = apply_relevant_timeouts_to(command(connection), connection)
|
53
|
+
Protocol::Msg.new(flags, {}, cmd, section)
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
@@ -14,11 +14,11 @@ module Mongo
|
|
14
14
|
# Returns the command to send to the database, describing the
|
15
15
|
# desired dropSearchIndex operation.
|
16
16
|
#
|
17
|
-
# @param [
|
17
|
+
# @param [ Connection ] _connection the connection that will receive the
|
18
18
|
# command
|
19
19
|
#
|
20
20
|
# @return [ Hash ] the selector
|
21
|
-
def selector(
|
21
|
+
def selector(_connection)
|
22
22
|
{
|
23
23
|
dropSearchIndex: coll_name,
|
24
24
|
:$db => db_name,
|
@@ -31,6 +31,51 @@ module Mongo
|
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
|
+
# Applies the relevant CSOT timeouts for a find command.
|
35
|
+
# Considers the cursor type and timeout mode and will add (or omit) a
|
36
|
+
# maxTimeMS field accordingly.
|
37
|
+
def apply_relevant_timeouts_to(spec, connection)
|
38
|
+
with_max_time(connection) do |max_time_sec|
|
39
|
+
timeout_ms = max_time_sec ? (max_time_sec * 1_000).to_i : nil
|
40
|
+
apply_find_timeouts_to(spec, timeout_ms) unless connection.description.mongocryptd?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def apply_find_timeouts_to(spec, timeout_ms)
|
45
|
+
view = context&.view
|
46
|
+
return spec unless view
|
47
|
+
|
48
|
+
case view.cursor_type
|
49
|
+
when nil # non-tailable
|
50
|
+
if view.timeout_mode == :cursor_lifetime
|
51
|
+
spec[:maxTimeMS] = timeout_ms || view.options[:max_time_ms]
|
52
|
+
else # timeout_mode == :iterable
|
53
|
+
# drivers MUST honor the timeoutMS option for the initial command
|
54
|
+
# but MUST NOT append a maxTimeMS field to the command sent to the
|
55
|
+
# server
|
56
|
+
if !timeout_ms && view.options[:max_time_ms]
|
57
|
+
spec[:maxTimeMS] = view.options[:max_time_ms]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
when :tailable
|
62
|
+
# If timeoutMS is set, drivers...MUST NOT append a maxTimeMS field to any commands.
|
63
|
+
if !timeout_ms && view.options[:max_time_ms]
|
64
|
+
spec[:maxTimeMS] = view.options[:max_time_ms]
|
65
|
+
end
|
66
|
+
|
67
|
+
when :tailable_await
|
68
|
+
# The server supports the maxTimeMS option for the original command.
|
69
|
+
if timeout_ms || view.options[:max_time_ms]
|
70
|
+
spec[:maxTimeMS] = timeout_ms || view.options[:max_time_ms]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
spec.tap do |spc|
|
75
|
+
spc.delete(:maxTimeMS) if spc[:maxTimeMS].nil?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
34
79
|
def selector(connection)
|
35
80
|
# The mappings are BSON::Documents and as such store keys as
|
36
81
|
# strings, the spec here has symbol keys.
|
@@ -28,6 +28,39 @@ module Mongo
|
|
28
28
|
include ExecutableTransactionLabel
|
29
29
|
include PolymorphicResult
|
30
30
|
include CommandBuilder
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Applies the relevant CSOT timeouts for a getMore command.
|
35
|
+
# Considers the cursor type and timeout mode and will add (or omit) a
|
36
|
+
# maxTimeMS field accordingly.
|
37
|
+
def apply_relevant_timeouts_to(spec, connection)
|
38
|
+
with_max_time(connection) do |max_time_sec|
|
39
|
+
timeout_ms = max_time_sec ? (max_time_sec * 1_000).to_i : nil
|
40
|
+
apply_get_more_timeouts_to(spec, timeout_ms)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def apply_get_more_timeouts_to(spec, timeout_ms)
|
45
|
+
view = context&.view
|
46
|
+
return spec unless view
|
47
|
+
|
48
|
+
if view.cursor_type == :tailable_await
|
49
|
+
# If timeoutMS is set, drivers MUST apply it to the original operation.
|
50
|
+
# Drivers MUST also apply the original timeoutMS value to each next
|
51
|
+
# call on the resulting cursor but MUST NOT use it to derive a
|
52
|
+
# maxTimeMS value for getMore commands. Helpers for operations that
|
53
|
+
# create tailable awaitData cursors MUST also support the
|
54
|
+
# maxAwaitTimeMS option. Drivers MUST error if this option is set,
|
55
|
+
# timeoutMS is set to a non-zero value, and maxAwaitTimeMS is greater
|
56
|
+
# than or equal to timeoutMS. If this option is set, drivers MUST use
|
57
|
+
# it as the maxTimeMS field on getMore commands.
|
58
|
+
max_await_time_ms = view.respond_to?(:max_await_time_ms) ? view.max_await_time_ms : nil
|
59
|
+
spec[:maxTimeMS] = max_await_time_ms if max_await_time_ms
|
60
|
+
end
|
61
|
+
|
62
|
+
spec
|
63
|
+
end
|
31
64
|
end
|
32
65
|
end
|
33
66
|
end
|
@@ -35,7 +35,7 @@ module Mongo
|
|
35
35
|
|
36
36
|
def get_result(connection, context, options = {})
|
37
37
|
# This is a Mongo::Operation::Insert::Result
|
38
|
-
Result.new(*dispatch_message(connection, context), @ids)
|
38
|
+
Result.new(*dispatch_message(connection, context), @ids, context: context)
|
39
39
|
end
|
40
40
|
|
41
41
|
def selector(connection)
|
@@ -49,7 +49,8 @@ module Mongo
|
|
49
49
|
|
50
50
|
def message(connection)
|
51
51
|
section = Protocol::Msg::Section1.new(IDENTIFIER, send(IDENTIFIER))
|
52
|
-
|
52
|
+
cmd = apply_relevant_timeouts_to(command(connection), connection)
|
53
|
+
Protocol::Msg.new(flags, {}, cmd, section)
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
@@ -47,11 +47,13 @@ module Mongo
|
|
47
47
|
# Global id of the connection on which the operation that
|
48
48
|
# this result is for was performed.
|
49
49
|
# @param [ Array<Object> ] ids The ids of the inserted documents.
|
50
|
+
# @param [ Operation::Context | nil ] context the operation context that
|
51
|
+
# was active when this result was produced.
|
50
52
|
#
|
51
53
|
# @since 2.0.0
|
52
54
|
# @api private
|
53
|
-
def initialize(replies, connection_description, connection_global_id, ids)
|
54
|
-
super(replies, connection_description, connection_global_id)
|
55
|
+
def initialize(replies, connection_description, connection_global_id, ids, context: nil)
|
56
|
+
super(replies, connection_description, connection_global_id, context: context)
|
55
57
|
@inserted_ids = ids
|
56
58
|
end
|
57
59
|
|
@@ -108,7 +108,7 @@ module Mongo
|
|
108
108
|
# @example Validate the result.
|
109
109
|
# result.validate!
|
110
110
|
#
|
111
|
-
# @raise [ Error::OperationFailure ] If an error is in the result.
|
111
|
+
# @raise [ Error::OperationFailure::Family ] If an error is in the result.
|
112
112
|
#
|
113
113
|
# @return [ Result ] The result if verification passed.
|
114
114
|
#
|
@@ -22,11 +22,13 @@ module Mongo
|
|
22
22
|
include Specifiable
|
23
23
|
include Executable
|
24
24
|
include SessionsSupported
|
25
|
+
include Timed
|
25
26
|
|
26
27
|
private
|
27
28
|
|
28
29
|
def message(connection)
|
29
|
-
|
30
|
+
cmd = apply_relevant_timeouts_to(command(connection), connection)
|
31
|
+
Protocol::Msg.new(flags, options(connection), cmd)
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|
@@ -100,9 +100,13 @@ module Mongo
|
|
100
100
|
# @param [ Integer ] connection_global_id
|
101
101
|
# Global id of the connection on which the operation that
|
102
102
|
# this result is for was performed.
|
103
|
+
# @param [ Operation::Context | nil ] context the context that was active
|
104
|
+
# when this result was produced.
|
103
105
|
#
|
104
106
|
# @api private
|
105
|
-
def initialize(replies, connection_description = nil, connection_global_id = nil)
|
107
|
+
def initialize(replies, connection_description = nil, connection_global_id = nil, context: nil, connection: nil)
|
108
|
+
@context = context
|
109
|
+
|
106
110
|
if replies
|
107
111
|
if replies.is_a?(Array)
|
108
112
|
if replies.length != 1
|
@@ -118,6 +122,7 @@ module Mongo
|
|
118
122
|
@replies = [ reply ]
|
119
123
|
@connection_description = connection_description
|
120
124
|
@connection_global_id = connection_global_id
|
125
|
+
@connection = connection
|
121
126
|
end
|
122
127
|
end
|
123
128
|
|
@@ -138,6 +143,14 @@ module Mongo
|
|
138
143
|
# @api private
|
139
144
|
attr_reader :connection_global_id
|
140
145
|
|
146
|
+
# @return [ Operation::Context | nil ] the operation context (if any)
|
147
|
+
# that was active when this result was produced.
|
148
|
+
#
|
149
|
+
# @api private
|
150
|
+
attr_reader :context
|
151
|
+
|
152
|
+
attr_reader :connection
|
153
|
+
|
141
154
|
# @api private
|
142
155
|
def_delegators :parser,
|
143
156
|
:not_master?, :node_recovering?, :node_shutting_down?
|
@@ -320,7 +333,7 @@ module Mongo
|
|
320
333
|
# @example Validate the result.
|
321
334
|
# result.validate!
|
322
335
|
#
|
323
|
-
# @raise [ Error::OperationFailure ] If an error is in the result.
|
336
|
+
# @raise [ Error::OperationFailure::Family ] If an error is in the result.
|
324
337
|
#
|
325
338
|
# @return [ Result ] The result if verification passed.
|
326
339
|
#
|
@@ -330,16 +343,16 @@ module Mongo
|
|
330
343
|
!successful? ? raise_operation_failure : self
|
331
344
|
end
|
332
345
|
|
333
|
-
# The exception instance (of
|
346
|
+
# The exception instance (of Error::OperationFailure::Family)
|
334
347
|
# that would be raised during processing of this result.
|
335
348
|
#
|
336
349
|
# This method should only be called when result is not successful.
|
337
350
|
#
|
338
|
-
# @return [ Error::OperationFailure ] The exception.
|
351
|
+
# @return [ Error::OperationFailure::Family ] The exception.
|
339
352
|
#
|
340
353
|
# @api private
|
341
354
|
def error
|
342
|
-
@error ||=
|
355
|
+
@error ||= operation_failure_class.new(
|
343
356
|
parser.message,
|
344
357
|
self,
|
345
358
|
code: parser.code,
|
@@ -453,6 +466,14 @@ module Mongo
|
|
453
466
|
|
454
467
|
private
|
455
468
|
|
469
|
+
def operation_failure_class
|
470
|
+
if context&.csot? && parser.code == 50
|
471
|
+
Error::ServerTimeoutError
|
472
|
+
else
|
473
|
+
Error::OperationFailure
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
456
477
|
def aggregate_returned_count
|
457
478
|
replies.reduce(0) do |n, reply|
|
458
479
|
n += reply.number_returned
|
@@ -28,7 +28,18 @@ module Mongo
|
|
28
28
|
|
29
29
|
include ResponseHandling
|
30
30
|
|
31
|
+
# @return [ Operation::Context | nil ] the operation context used to
|
32
|
+
# execute this operation.
|
33
|
+
attr_accessor :context
|
34
|
+
|
31
35
|
def do_execute(connection, context, options = {})
|
36
|
+
# Save the context on the instance, to avoid having to pass it as a
|
37
|
+
# parameter to every single method. There are many legacy methods that
|
38
|
+
# still accept it as a parameter, which are left as-is for now to
|
39
|
+
# minimize the impact of this change. Moving forward, it may be
|
40
|
+
# reasonable to refactor things so this saved reference is used instead.
|
41
|
+
@context = context
|
42
|
+
|
32
43
|
session&.materialize_if_needed
|
33
44
|
unpin_maybe(session, connection) do
|
34
45
|
add_error_labels(connection, context) do
|
@@ -93,7 +104,7 @@ module Mongo
|
|
93
104
|
end
|
94
105
|
|
95
106
|
def get_result(connection, context, options = {})
|
96
|
-
result_class.new(*dispatch_message(connection, context, options))
|
107
|
+
result_class.new(*dispatch_message(connection, context, options), context: context, connection: connection)
|
97
108
|
end
|
98
109
|
|
99
110
|
# Returns a Protocol::Message or nil as reply.
|
@@ -32,7 +32,10 @@ module Mongo
|
|
32
32
|
#
|
33
33
|
# @return [ Mongo::Operation::Result ] The operation result.
|
34
34
|
def execute(server, context:, options: {})
|
35
|
-
server.with_connection(
|
35
|
+
server.with_connection(
|
36
|
+
connection_global_id: context.connection_global_id,
|
37
|
+
context: context
|
38
|
+
) do |connection|
|
36
39
|
execute_with_connection(connection, context: context, options: options)
|
37
40
|
end
|
38
41
|
end
|