mongo 2.21.3 → 2.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mongo/address.rb +8 -2
  3. data/lib/mongo/bulk_write/transformable.rb +2 -0
  4. data/lib/mongo/client.rb +40 -4
  5. data/lib/mongo/cluster.rb +4 -1
  6. data/lib/mongo/collection/view/aggregation/behavior.rb +1 -1
  7. data/lib/mongo/collection/view/aggregation.rb +5 -2
  8. data/lib/mongo/collection/view/iterable.rb +16 -14
  9. data/lib/mongo/collection/view/readable.rb +64 -55
  10. data/lib/mongo/collection/view/writable.rb +64 -46
  11. data/lib/mongo/collection/view.rb +2 -0
  12. data/lib/mongo/collection.rb +66 -46
  13. data/lib/mongo/config.rb +4 -0
  14. data/lib/mongo/crypt/auto_decryption_context.rb +9 -0
  15. data/lib/mongo/crypt/binding.rb +1 -1
  16. data/lib/mongo/crypt/context.rb +10 -0
  17. data/lib/mongo/crypt/explicit_decryption_context.rb +9 -0
  18. data/lib/mongo/database/view.rb +25 -20
  19. data/lib/mongo/database.rb +17 -10
  20. data/lib/mongo/deprecations.rb +98 -0
  21. data/lib/mongo/index/view.rb +28 -19
  22. data/lib/mongo/operation/create.rb +4 -0
  23. data/lib/mongo/operation/insert/op_msg.rb +5 -2
  24. data/lib/mongo/operation/shared/executable.rb +11 -4
  25. data/lib/mongo/operation/shared/specifiable.rb +5 -1
  26. data/lib/mongo/search_index/view.rb +29 -9
  27. data/lib/mongo/server/app_metadata/platform.rb +17 -4
  28. data/lib/mongo/server/connection.rb +18 -0
  29. data/lib/mongo/server/description/features.rb +37 -8
  30. data/lib/mongo/server.rb +5 -2
  31. data/lib/mongo/session.rb +55 -19
  32. data/lib/mongo/socket.rb +1 -1
  33. data/lib/mongo/srv/monitor.rb +5 -1
  34. data/lib/mongo/srv/result.rb +14 -4
  35. data/lib/mongo/tracing/open_telemetry/command_tracer.rb +320 -0
  36. data/lib/mongo/tracing/open_telemetry/operation_tracer.rb +227 -0
  37. data/lib/mongo/tracing/open_telemetry/tracer.rb +236 -0
  38. data/lib/mongo/tracing/open_telemetry.rb +27 -0
  39. data/lib/mongo/tracing.rb +42 -0
  40. data/lib/mongo/uri/srv_protocol.rb +11 -6
  41. data/lib/mongo/version.rb +1 -1
  42. data/lib/mongo.rb +3 -0
  43. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa269cb6bbc5d94255ef1dfb56b4c963304437f9ddcc064b43545485a9892ffa
4
- data.tar.gz: e0fe8f3cadadc8ef70a28d82ea512bd779d9380738bdb814d7d8c8f59618b65d
3
+ metadata.gz: e69a3d0fe1586e73885c27a97616bb2422847a13a6b86e1c7f95348261d7de58
4
+ data.tar.gz: ff6fc65770491d22c49221140e248d37edbb58c3715808843718cb2b9fc8e331
5
5
  SHA512:
6
- metadata.gz: 6819366f273f957c46eb5a9dae0a4de7cfe12f1bfea1fe60c412631650d0337d19e8f543d5608b04e8a2c7c57cb84ce3c5598ccaca0eec0984a8dddc9876c5d8
7
- data.tar.gz: 2aebef8f8d12d6770b5e07cd516909d29762d76352518b7dfa79433d3eaf56fd9871086c8531e4dad21c70829bf268c7f834c2cf9f3bbbbf01fe787a803f2dc3
6
+ metadata.gz: fc176435fcf633b0b3acd905bcbca387054f70210c3d140223b30eadbed7667e56ee3ae868ad003d47b89992de09da19fc0bcfc1009f186b8e4bbda1caead6a3
7
+ data.tar.gz: c5ca31db40bde02be00178be089fe7c7f47504374fd606b84696ddf3ae94d14bc7f2efb00a6c7093a224451022a0098e7528268b364a731e9d95db72f3f23ddf
data/lib/mongo/address.rb CHANGED
@@ -237,7 +237,7 @@ module Mongo
237
237
  # (multiple identical items in the returned array). It does not make
238
238
  # sense to try to connect to the same address more than once, thus
239
239
  # eliminate duplicates here.
240
- infos = ::Socket.getaddrinfo(host, nil, family, ::Socket::SOCK_STREAM)
240
+ infos = getaddrinfo(host, family)
241
241
  results = infos.map do |info|
242
242
  [info[4], info[3]]
243
243
  end.uniq
@@ -276,6 +276,12 @@ module Mongo
276
276
 
277
277
  private
278
278
 
279
+ # This is a simple wrapper around Socket.getaddrinfo added to
280
+ # make testing easier.
281
+ def getaddrinfo(host, family)
282
+ ::Socket.getaddrinfo(host, nil, family, ::Socket::SOCK_STREAM)
283
+ end
284
+
279
285
  def parse_host_port
280
286
  address = seed.downcase
281
287
  case address
@@ -305,7 +311,7 @@ module Mongo
305
311
  else
306
312
  raise e
307
313
  end
308
- rescue IOError, SystemCallError => e
314
+ rescue IOError, SystemCallError, ::SocketError => e
309
315
  raise Error::SocketError, "#{e.class}: #{e} (for #{self})"
310
316
  rescue OpenSSL::SSL::SSLError => e
311
317
  raise Error::SocketError, "#{e.class}: #{e} (for #{self})"
@@ -99,6 +99,7 @@ module Mongo
99
99
  d['upsert'] = true if doc[:upsert]
100
100
  d[Operation::COLLATION] = doc[:collation] if doc[:collation]
101
101
  d['hint'] = doc[:hint] if doc[:hint]
102
+ d['sort'] = doc[:sort] if doc[:sort]
102
103
  end
103
104
  }
104
105
 
@@ -130,6 +131,7 @@ module Mongo
130
131
  d[Operation::COLLATION] = doc[:collation] if doc[:collation]
131
132
  d[Operation::ARRAY_FILTERS] = doc[:array_filters] if doc[:array_filters]
132
133
  d['hint'] = doc[:hint] if doc[:hint]
134
+ d['sort'] = doc[:sort] if doc[:sort]
133
135
  end
134
136
  }
135
137
 
data/lib/mongo/client.rb CHANGED
@@ -112,6 +112,7 @@ module Mongo
112
112
  :ssl_verify_hostname,
113
113
  :ssl_verify_ocsp_endpoint,
114
114
  :timeout_ms,
115
+ :tracing,
115
116
  :truncate_logs,
116
117
  :user,
117
118
  :wait_queue_timeout,
@@ -356,7 +357,9 @@ module Mongo
356
357
  # that the driver will communicate with for sharded topologies. If this
357
358
  # option is 0, then there will be no maximum number of mongoses. If the
358
359
  # given URI resolves to more hosts than ``:srv_max_hosts``, the client
359
- # will ramdomly choose an ``:srv_max_hosts`` sized subset of hosts.
360
+ # will randomly choose an ``:srv_max_hosts`` sized subset of hosts. If
361
+ # srvMaxHosts is provided in the URI options, it takes precedence over this
362
+ # option.
360
363
  # @option options [ String ] :srv_service_name The service name to use in
361
364
  # the SRV DNS query.
362
365
  # @option options [ true, false ] :ssl Whether to use TLS.
@@ -437,6 +440,20 @@ module Mongo
437
440
  # See Ruby's Zlib module for valid levels.
438
441
  # @option options [ Hash ] :resolv_options For internal driver use only.
439
442
  # Options to pass through to Resolv::DNS constructor for SRV lookups.
443
+ # @option options [ Hash ] :tracing OpenTelemetry tracing options.
444
+ # - :enabled => Boolean, whether to enable OpenTelemetry tracing. The default
445
+ # value is nil that means that the configuration will be taken from the
446
+ # OTEL_RUBY_INSTRUMENTATION_MONGODB_ENABLED environment variable.
447
+ # - :tracer => OpenTelemetry::Trace::Tracer, the tracer to use for
448
+ # tracing. Must be an implementation of OpenTelemetry::Trace::Tracer
449
+ # interface.
450
+ # - :query_text_max_length => Integer, the maximum length of the query text
451
+ # to be included in the span attributes. If the query text exceeds this
452
+ # length, it will be truncated. Value 0 means no query text
453
+ # will be included in the span attributes. The default value is nil that
454
+ # means that the configuration will be taken from the
455
+ # OTEL_RUBY_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH environment
456
+ # variable.
440
457
  # @option options [ Hash ] :auto_encryption_options Auto-encryption related
441
458
  # options.
442
459
  # - :key_vault_client => Client | nil, a client connected to the MongoDB
@@ -574,8 +591,11 @@ module Mongo
574
591
 
575
592
  @connect_lock = Mutex.new
576
593
  @connect_lock.synchronize do
577
- @cluster = Cluster.new(addresses, @monitoring,
578
- cluster_options.merge(srv_uri: srv_uri))
594
+ @cluster = Cluster.new(
595
+ addresses,
596
+ @monitoring,
597
+ cluster_options.merge(srv_uri: srv_uri)
598
+ )
579
599
  end
580
600
 
581
601
  begin
@@ -623,6 +643,7 @@ module Mongo
623
643
  # applications should read these values from client, not from cluster
624
644
  max_read_retries: options[:max_read_retries],
625
645
  read_retry_interval: options[:read_retry_interval],
646
+ tracer: tracer,
626
647
  ).tap do |options|
627
648
  # If the client has a cluster already, forward srv_uri to the new
628
649
  # cluster to maintain SRV monitoring. If the client is brand new,
@@ -965,7 +986,10 @@ module Mongo
965
986
  cmd[:nameOnly] = !!name_only
966
987
  cmd[:filter] = filter unless filter.empty?
967
988
  cmd[:authorizedDatabases] = true if opts[:authorized_databases]
968
- use(Database::ADMIN).database.read_command(cmd, opts).first[Database::DATABASES]
989
+ use(Database::ADMIN)
990
+ .database
991
+ .read_command(cmd, opts.merge(op_name: 'listDatabases'))
992
+ .first[Database::DATABASES]
969
993
  end
970
994
 
971
995
  # Returns a list of Mongo::Database objects.
@@ -1195,6 +1219,18 @@ module Mongo
1195
1219
  end
1196
1220
  end
1197
1221
 
1222
+ # Get the tracer configured for this client.
1223
+ #
1224
+ # @return [ Tracing::Tracer | nil ] The tracer configured for this client.
1225
+ def tracer
1226
+ tracing_opts = @options[:tracing] || {}
1227
+ @tracer ||= Tracing.create_tracer(
1228
+ enabled: tracing_opts[:enabled],
1229
+ query_text_max_length: tracing_opts[:query_text_max_length],
1230
+ otel_tracer: tracing_opts[:tracer],
1231
+ )
1232
+ end
1233
+
1198
1234
  private
1199
1235
 
1200
1236
  # Attempts to parse the given list of addresses, using the provided options.
data/lib/mongo/cluster.rb CHANGED
@@ -126,6 +126,7 @@ module Mongo
126
126
  if options[:monitoring_io] == false && !options.key?(:cleanup)
127
127
  options[:cleanup] = false
128
128
  end
129
+ @tracer = options.delete(:tracer)
129
130
  @options = options.freeze
130
131
 
131
132
  # @update_lock covers @servers, @connecting, @connected, @topology and
@@ -298,7 +299,7 @@ module Mongo
298
299
  cluster = Cluster.new(
299
300
  client.cluster.addresses.map(&:to_s),
300
301
  monitoring || Monitoring.new,
301
- client.cluster_options,
302
+ client.cluster_options
302
303
  )
303
304
  client.instance_variable_set(:@cluster, cluster)
304
305
  end
@@ -309,6 +310,8 @@ module Mongo
309
310
  # @return [ Monitoring ] monitoring The monitoring.
310
311
  attr_reader :monitoring
311
312
 
313
+ attr_reader :tracer
314
+
312
315
  # @return [ Object ] The cluster topology.
313
316
  attr_reader :topology
314
317
 
@@ -88,7 +88,7 @@ module Mongo
88
88
  @view.send(:server_selector)
89
89
  end
90
90
 
91
- def aggregate_spec(session, read_preference)
91
+ def aggregate_spec(session, read_preference = nil)
92
92
  Builder::Aggregation.new(
93
93
  pipeline,
94
94
  view,
@@ -25,11 +25,14 @@ module Mongo
25
25
  #
26
26
  # @since 2.0.0
27
27
  class Aggregation
28
+ extend Forwardable
28
29
  include Behavior
29
30
 
30
31
  # @return [ Array<Hash> ] pipeline The aggregation pipeline.
31
32
  attr_reader :pipeline
32
33
 
34
+ def_delegators :view, :tracer
35
+
33
36
  # Initialize the aggregation for the provided collection view, pipeline
34
37
  # and options.
35
38
  #
@@ -80,7 +83,7 @@ module Mongo
80
83
  Aggregation.new(view, pipeline, options)
81
84
  end
82
85
 
83
- def initial_query_op(session, read_preference)
86
+ def initial_query_op(session, read_preference = nil)
84
87
  Operation::Aggregate.new(aggregate_spec(session, read_preference))
85
88
  end
86
89
 
@@ -117,7 +120,7 @@ module Mongo
117
120
 
118
121
  end
119
122
 
120
- def send_initial_query(server, context)
123
+ def send_initial_query(server, context, operation: nil)
121
124
  if server.load_balancer?
122
125
  # Connection will be checked in when cursor is drained.
123
126
  connection = server.pool.check_out(context: context)
@@ -88,19 +88,21 @@ module Mongo
88
88
  operation_timeouts: operation_timeouts,
89
89
  view: self
90
90
  )
91
-
92
- if respond_to?(:write?, true) && write?
93
- server = server_selector.select_server(cluster, nil, session, write_aggregation: true)
94
- result = send_initial_query(server, context)
95
-
96
- if use_query_cache?
97
- CachingCursor.new(view, result, server, session: session, context: context)
91
+ op = initial_query_op(session)
92
+ tracer.trace_operation(op, context) do
93
+ if respond_to?(:write?, true) && write?
94
+ server = server_selector.select_server(cluster, nil, session, write_aggregation: true)
95
+ result = send_initial_query(server, context, operation: op)
96
+
97
+ if use_query_cache?
98
+ CachingCursor.new(view, result, server, session: session, context: context)
99
+ else
100
+ Cursor.new(view, result, server, session: session, context: context)
101
+ end
98
102
  else
99
- Cursor.new(view, result, server, session: session, context: context)
100
- end
101
- else
102
- read_with_retry_cursor(session, server_selector, view, context: context) do |server|
103
- send_initial_query(server, context)
103
+ read_with_retry_cursor(session, server_selector, view, context: context) do |server|
104
+ send_initial_query(server, context, operation: op)
105
+ end
104
106
  end
105
107
  end
106
108
  end
@@ -167,8 +169,8 @@ module Mongo
167
169
  end
168
170
  end
169
171
 
170
- def send_initial_query(server, context)
171
- operation = initial_query_op(context.session)
172
+ def send_initial_query(server, context, operation: nil)
173
+ operation ||= initial_query_op(context.session)
172
174
  if server.load_balancer?
173
175
  # Connection will be checked in when cursor is drained.
174
176
  connection = server.pool.check_out(context: context)
@@ -192,22 +192,25 @@ module Mongo
192
192
  session: session,
193
193
  operation_timeouts: operation_timeouts(opts)
194
194
  )
195
- read_with_retry(session, selector, context) do |server|
196
- Operation::Count.new(
197
- selector: cmd,
198
- db_name: database.name,
199
- options: {:limit => -1},
200
- read: read_pref,
201
- session: session,
202
- # For some reason collation was historically accepted as a
203
- # string key. Note that this isn't documented as valid usage.
204
- collation: opts[:collation] || opts['collation'] || collation,
205
- comment: opts[:comment],
206
- ).execute(
207
- server,
208
- context: context
209
- )
210
- end.n.to_i
195
+ operation = Operation::Count.new(
196
+ selector: cmd,
197
+ db_name: database.name,
198
+ options: {:limit => -1},
199
+ read: read_pref,
200
+ session: session,
201
+ # For some reason collation was historically accepted as a
202
+ # string key. Note that this isn't documented as valid usage.
203
+ collation: opts[:collation] || opts['collation'] || collation,
204
+ comment: opts[:comment],
205
+ )
206
+ tracer.trace_operation(operation, context) do
207
+ read_with_retry(session, selector, context) do |server|
208
+ operation.execute(
209
+ server,
210
+ context: context
211
+ )
212
+ end.n.to_i
213
+ end
211
214
  end
212
215
  end
213
216
 
@@ -294,32 +297,35 @@ module Mongo
294
297
  session: session,
295
298
  operation_timeouts: operation_timeouts(opts)
296
299
  )
297
- read_with_retry(session, selector, context) do |server|
298
- cmd = { count: collection.name }
299
- cmd[:maxTimeMS] = opts[:max_time_ms] if opts[:max_time_ms]
300
- if read_concern
301
- cmd[:readConcern] = Options::Mapper.transform_values_to_strings(read_concern)
300
+ cmd = { count: collection.name }
301
+ cmd[:maxTimeMS] = opts[:max_time_ms] if opts[:max_time_ms]
302
+ if read_concern
303
+ cmd[:readConcern] = Options::Mapper.transform_values_to_strings(read_concern)
304
+ end
305
+ operation = Operation::Count.new(
306
+ selector: cmd,
307
+ db_name: database.name,
308
+ read: read_pref,
309
+ session: session,
310
+ comment: opts[:comment],
311
+ )
312
+ tracer.trace_operation(operation, context, op_name: 'estimatedDocumentCount') do
313
+ read_with_retry(session, selector, context) do |server|
314
+ result = operation.execute(server, context: context)
315
+ result.n.to_i
316
+ end
317
+ rescue Error::OperationFailure::Family => exc
318
+ if exc.code == 26
319
+ # NamespaceNotFound
320
+ # This should only happen with the aggregation pipeline path
321
+ # (server 4.9+). Previous servers should return 0 for nonexistent
322
+ # collections.
323
+ 0
324
+ else
325
+ raise
302
326
  end
303
- result = Operation::Count.new(
304
- selector: cmd,
305
- db_name: database.name,
306
- read: read_pref,
307
- session: session,
308
- comment: opts[:comment],
309
- ).execute(server, context: context)
310
- result.n.to_i
311
327
  end
312
328
  end
313
- rescue Error::OperationFailure::Family => exc
314
- if exc.code == 26
315
- # NamespaceNotFound
316
- # This should only happen with the aggregation pipeline path
317
- # (server 4.9+). Previous servers should return 0 for nonexistent
318
- # collections.
319
- 0
320
- else
321
- raise
322
- end
323
329
  end
324
330
 
325
331
  # Get a list of distinct values for a specific field.
@@ -362,22 +368,25 @@ module Mongo
362
368
  session: session,
363
369
  operation_timeouts: operation_timeouts(opts)
364
370
  )
365
- read_with_retry(session, selector, context) do |server|
366
- Operation::Distinct.new(
367
- selector: cmd,
368
- db_name: database.name,
369
- options: {:limit => -1},
370
- read: read_pref,
371
- session: session,
372
- comment: opts[:comment],
373
- # For some reason collation was historically accepted as a
374
- # string key. Note that this isn't documented as valid usage.
375
- collation: opts[:collation] || opts['collation'] || collation,
376
- ).execute(
377
- server,
378
- context: context
379
- )
380
- end.first['values']
371
+ operation = Operation::Distinct.new(
372
+ selector: cmd,
373
+ db_name: database.name,
374
+ options: {:limit => -1},
375
+ read: read_pref,
376
+ session: session,
377
+ comment: opts[:comment],
378
+ # For some reason collation was historically accepted as a
379
+ # string key. Note that this isn't documented as valid usage.
380
+ collation: opts[:collation] || opts['collation'] || collation,
381
+ )
382
+ tracer.trace_operation(operation, context) do
383
+ read_with_retry(session, selector, context) do |server|
384
+ operation.execute(
385
+ server,
386
+ context: context
387
+ )
388
+ end.first['values']
389
+ end
381
390
  end
382
391
  end
383
392
 
@@ -211,22 +211,24 @@ module Mongo
211
211
  session: session,
212
212
  operation_timeouts: operation_timeouts(opts)
213
213
  )
214
- write_with_retry(write_concern, context: context) do |connection, txn_num, context|
215
- gte_4_4 = connection.server.description.server_version_gte?('4.4')
216
- if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
217
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
214
+ operation = Operation::WriteCommand.new(
215
+ selector: cmd,
216
+ db_name: database.name,
217
+ write_concern: write_concern,
218
+ session: session,
219
+ )
220
+ value = tracer.trace_operation(operation, context, op_name: 'findOneAndUpdate') do
221
+ write_with_retry(write_concern, context: context) do |connection, txn_num, context|
222
+ gte_4_4 = connection.server.description.server_version_gte?('4.4')
223
+ if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
224
+ raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
225
+ end
226
+ operation.txn_num = txn_num
227
+ operation.execute_with_connection(connection, context: context)
218
228
  end
219
-
220
- Operation::WriteCommand.new(
221
- selector: cmd,
222
- db_name: database.name,
223
- write_concern: write_concern,
224
- session: session,
225
- txn_num: txn_num,
226
- ).execute_with_connection(connection, context: context)
227
- end
228
- end.first&.fetch('value', nil)
229
- value unless value.nil? || value.empty?
229
+ end.first&.fetch('value', nil)
230
+ value unless value.nil? || value.empty?
231
+ end
230
232
  end
231
233
 
232
234
  # Remove documents from the collection.
@@ -275,22 +277,24 @@ module Mongo
275
277
  session: session,
276
278
  operation_timeouts: operation_timeouts(opts)
277
279
  )
278
- nro_write_with_retry(write_concern, context: context) do |connection, txn_num, context|
279
- gte_4_4 = connection.server.description.server_version_gte?('4.4')
280
- if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
281
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
280
+ operation = Operation::Delete.new(
281
+ deletes: [ delete_doc ],
282
+ db_name: collection.database.name,
283
+ coll_name: collection.name,
284
+ write_concern: write_concern,
285
+ bypass_document_validation: !!opts[:bypass_document_validation],
286
+ session: session,
287
+ let: opts[:let],
288
+ comment: opts[:comment],
289
+ )
290
+ tracer.trace_operation(operation, context, op_name: 'deleteMany') do
291
+ nro_write_with_retry(write_concern, context: context) do |connection, txn_num, context|
292
+ gte_4_4 = connection.server.description.server_version_gte?('4.4')
293
+ if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
294
+ raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
295
+ end
296
+ operation.execute_with_connection(connection, context: context)
282
297
  end
283
-
284
- Operation::Delete.new(
285
- deletes: [ delete_doc ],
286
- db_name: collection.database.name,
287
- coll_name: collection.name,
288
- write_concern: write_concern,
289
- bypass_document_validation: !!opts[:bypass_document_validation],
290
- session: session,
291
- let: opts[:let],
292
- comment: opts[:comment],
293
- ).execute_with_connection(connection, context: context)
294
298
  end
295
299
  end
296
300
  end
@@ -389,6 +393,11 @@ module Mongo
389
393
  # @option opts [ true, false ] :upsert Whether to upsert if the
390
394
  # document doesn't exist.
391
395
  # Can be :w => Integer, :fsync => Boolean, :j => Boolean.
396
+ # @option opts [ Hash ] :sort Specifies which document the operation
397
+ # replaces if the query matches multiple documents. The first document
398
+ # matched by the sort order will be replaced.
399
+ # This option is only supported by servers >= 8.0. Older servers will
400
+ # report an error for using this option.
392
401
  #
393
402
  # @return [ Result ] The response from the database.
394
403
  #
@@ -410,6 +419,7 @@ module Mongo
410
419
  Operation::U => replacement,
411
420
  hint: opts[:hint],
412
421
  collation: opts[:collation] || opts['collation'] || collation,
422
+ sort: opts[:sort] || opts['sort'],
413
423
  }.compact
414
424
  if opts[:upsert]
415
425
  update_doc['upsert'] = true
@@ -549,6 +559,11 @@ module Mongo
549
559
  # document doesn't exist.
550
560
  # @option opts [ Hash ] :write_concern The write concern options.
551
561
  # Can be :w => Integer, :fsync => Boolean, :j => Boolean.
562
+ # @option opts [ Hash ] :sort Specifies which document the operation
563
+ # updates if the query matches multiple documents. The first document
564
+ # matched by the sort order will be updated.
565
+ # This option is only supported by servers >= 8.0. Older servers will
566
+ # report an error for using this option.
552
567
  #
553
568
  # @return [ Result ] The response from the database.
554
569
  #
@@ -570,6 +585,7 @@ module Mongo
570
585
  Operation::U => spec,
571
586
  hint: opts[:hint],
572
587
  collation: opts[:collation] || opts['collation'] || collation,
588
+ sort: opts[:sort] || opts['sort'],
573
589
  }.compact
574
590
  if opts[:upsert]
575
591
  update_doc['upsert'] = true
@@ -580,23 +596,25 @@ module Mongo
580
596
  session: session,
581
597
  operation_timeouts: operation_timeouts(opts)
582
598
  )
583
- write_with_retry(write_concern, context: context) do |connection, txn_num, context|
584
- gte_4_2 = connection.server.description.server_version_gte?('4.2')
585
- if !gte_4_2 && opts[:hint] && write_concern && !write_concern.acknowledged?
586
- raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
599
+ operation = Operation::Update.new(
600
+ updates: [ update_doc ],
601
+ db_name: collection.database.name,
602
+ coll_name: collection.name,
603
+ write_concern: write_concern,
604
+ bypass_document_validation: !!opts[:bypass_document_validation],
605
+ session: session,
606
+ let: opts[:let],
607
+ comment: opts[:comment],
608
+ )
609
+ tracer.trace_operation(operation, context) do
610
+ write_with_retry(write_concern, context: context) do |connection, txn_num, context|
611
+ gte_4_2 = connection.server.description.server_version_gte?('4.2')
612
+ if !gte_4_2 && opts[:hint] && write_concern && !write_concern.acknowledged?
613
+ raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
614
+ end
615
+ operation.txn_num = txn_num
616
+ operation.execute_with_connection(connection, context: context)
587
617
  end
588
-
589
- Operation::Update.new(
590
- updates: [ update_doc ],
591
- db_name: collection.database.name,
592
- coll_name: collection.name,
593
- write_concern: write_concern,
594
- bypass_document_validation: !!opts[:bypass_document_validation],
595
- session: session,
596
- txn_num: txn_num,
597
- let: opts[:let],
598
- comment: opts[:comment],
599
- ).execute_with_connection(connection, context: context)
600
618
  end
601
619
  end
602
620
  end
@@ -72,6 +72,8 @@ module Mongo
72
72
  # Delegate to the cluster for the next primary.
73
73
  def_delegators :cluster, :next_primary
74
74
 
75
+ def_delegators :client, :tracer
76
+
75
77
  alias :selector :filter
76
78
 
77
79
  # @return [ Integer | nil | The timeout_ms value that was passed as an