mongo 2.25.0 → 2.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mongo/bulk_write/combineable.rb +0 -7
  3. data/lib/mongo/bulk_write/validatable.rb +0 -4
  4. data/lib/mongo/bulk_write.rb +0 -34
  5. data/lib/mongo/client.rb +35 -5
  6. data/lib/mongo/client_encryption.rb +36 -11
  7. data/lib/mongo/cluster.rb +1 -2
  8. data/lib/mongo/collection/queryable_encryption.rb +7 -6
  9. data/lib/mongo/collection/view/aggregation.rb +9 -9
  10. data/lib/mongo/collection/view/change_stream.rb +1 -5
  11. data/lib/mongo/collection/view/iterable.rb +2 -9
  12. data/lib/mongo/collection/view/map_reduce.rb +5 -4
  13. data/lib/mongo/collection/view/readable.rb +3 -3
  14. data/lib/mongo/collection/view/writable.rb +0 -35
  15. data/lib/mongo/crypt/auto_encrypter.rb +5 -1
  16. data/lib/mongo/crypt/binding.rb +66 -1
  17. data/lib/mongo/crypt/explicit_encrypter.rb +27 -12
  18. data/lib/mongo/crypt/explicit_encryption_context.rb +51 -12
  19. data/lib/mongo/crypt/handle.rb +18 -0
  20. data/lib/mongo/cursor.rb +13 -0
  21. data/lib/mongo/database/view.rb +7 -6
  22. data/lib/mongo/database.rb +7 -10
  23. data/lib/mongo/error/unsupported_option.rb +0 -40
  24. data/lib/mongo/grid/fs_bucket.rb +2 -2
  25. data/lib/mongo/grid/stream/read.rb +2 -2
  26. data/lib/mongo/grid/stream/write.rb +1 -1
  27. data/lib/mongo/index/view.rb +3 -2
  28. data/lib/mongo/monitoring/event/secure.rb +6 -1
  29. data/lib/mongo/operation/map_reduce/result.rb +6 -2
  30. data/lib/mongo/operation/result.rb +9 -0
  31. data/lib/mongo/operation/shared/executable.rb +1 -6
  32. data/lib/mongo/operation/shared/result/aggregatable.rb +3 -3
  33. data/lib/mongo/protocol/message.rb +0 -4
  34. data/lib/mongo/protocol/msg.rb +1 -12
  35. data/lib/mongo/protocol/serializers.rb +1 -9
  36. data/lib/mongo/retryable/backpressure.rb +17 -2
  37. data/lib/mongo/retryable/read_worker.rb +1 -1
  38. data/lib/mongo/retryable/retry_policy.rb +2 -2
  39. data/lib/mongo/retryable/write_worker.rb +2 -2
  40. data/lib/mongo/retryable.rb +1 -1
  41. data/lib/mongo/server/app_metadata.rb +1 -2
  42. data/lib/mongo/server/connection.rb +7 -0
  43. data/lib/mongo/server/connection_pool.rb +60 -9
  44. data/lib/mongo/server/description/features.rb +2 -3
  45. data/lib/mongo/server/description.rb +0 -1
  46. data/lib/mongo/server/pending_connection.rb +0 -5
  47. data/lib/mongo/server.rb +1 -2
  48. data/lib/mongo/session.rb +10 -3
  49. data/lib/mongo/tracing/open_telemetry/command_tracer.rb +141 -102
  50. data/lib/mongo/tracing/open_telemetry/operation_tracer.rb +52 -17
  51. data/lib/mongo/tracing/open_telemetry/tracer.rb +10 -1
  52. data/lib/mongo/uri/options_mapper.rb +15 -4
  53. data/lib/mongo/version.rb +1 -1
  54. metadata +2 -2
@@ -37,6 +37,8 @@ module Mongo
37
37
  def initialize(otel_tracer, parent_tracer)
38
38
  @otel_tracer = otel_tracer
39
39
  @parent_tracer = parent_tracer
40
+ @operation_names = {}
41
+ @operation_names_mutex = Mutex.new
40
42
  end
41
43
 
42
44
  # Trace a MongoDB operation.
@@ -53,21 +55,43 @@ module Mongo
53
55
  # @yield the block representing the operation to be traced.
54
56
  #
55
57
  # @return [ Object ] the result of the operation.
56
- #
57
- # rubocop:disable Lint/RescueException
58
58
  def trace_operation(operation, operation_context, op_name: nil, &block)
59
59
  span = create_operation_span(operation, operation_context, op_name)
60
- execute_with_span(span, operation, &block)
60
+ # With no SDK and no valid parent, the API returns a span with an
61
+ # invalid context. It cannot be made current, recorded or
62
+ # propagated, so it gets no work at all, not even finishing it.
63
+ return yield unless span.context.valid?
64
+
65
+ trace_with_span(span, operation, &block)
66
+ end
67
+
68
+ private
69
+
70
+ # Runs the operation with the span made current, and finishes the span.
71
+ # A span that is not recording is still made current, so that the
72
+ # command spans created within it nest under it, but gets no further
73
+ # attributes and no outcome: the SDK would discard them.
74
+ #
75
+ # @param span [ OpenTelemetry::Trace::Span ] a span with a valid context.
76
+ # @param operation [ Mongo::Operation ] the operation.
77
+ #
78
+ # @return [ Object ] the result of the operation.
79
+ #
80
+ # rubocop:disable Lint/RescueException
81
+ def trace_with_span(span, operation, &block)
82
+ recording = span.recording?
83
+ if recording && !operation.cursor_id.nil?
84
+ span.set_attribute('db.mongodb.cursor_id', operation.cursor_id)
85
+ end
86
+ execute_with_span(span, operation, recording, &block)
61
87
  rescue Exception => e
62
- handle_span_exception(span, e)
88
+ handle_span_exception(span, e) if recording
63
89
  raise e
64
90
  ensure
65
- span&.finish
91
+ span.finish
66
92
  end
67
93
  # rubocop:enable Lint/RescueException
68
94
 
69
- private
70
-
71
95
  # Creates an OpenTelemetry span for the operation.
72
96
  #
73
97
  # @param operation [ Mongo::Operation ] the operation.
@@ -89,14 +113,15 @@ module Mongo
89
113
  #
90
114
  # @param span [ OpenTelemetry::Trace::Span ] the span.
91
115
  # @param operation [ Mongo::Operation ] the operation.
116
+ # @param recording [ true | false ] whether the span is recording.
92
117
  #
93
118
  # @yield the block to execute.
94
119
  #
95
120
  # @return [ Object ] the result of the block.
96
- def execute_with_span(span, operation)
121
+ def execute_with_span(span, operation, recording)
97
122
  ::OpenTelemetry::Trace.with_span(span) do |s, c|
98
123
  yield.tap do |result|
99
- process_cursor_context(result, operation.cursor_id, c, s)
124
+ process_cursor_context(result, operation.cursor_id, c, s, recording)
100
125
  end
101
126
  end
102
127
  end
@@ -106,8 +131,6 @@ module Mongo
106
131
  # @param span [ OpenTelemetry::Trace::Span ] the span.
107
132
  # @param exception [ Exception ] the exception.
108
133
  def handle_span_exception(span, exception)
109
- return unless span
110
-
111
134
  span.record_exception(exception)
112
135
  span.status = ::OpenTelemetry::Trace::Status.error(
113
136
  "Unhandled exception of type: #{exception.class}"
@@ -115,16 +138,28 @@ module Mongo
115
138
  end
116
139
 
117
140
  # Returns the operation name from the provided name or operation class.
141
+ # The name derived from the class is cached per class. There are few
142
+ # operation classes, so the cache needs no bound. It is guarded by a
143
+ # mutex because not every Ruby runtime makes Hash writes thread-safe.
118
144
  #
119
145
  # @param operation [ Mongo::Operation ] the operation.
120
146
  # @param op_name [ String | nil ] optional operation name.
121
147
  #
122
148
  # @return [ String ] the operation name in lowercase.
123
149
  def operation_name(operation, op_name = nil)
124
- op_name || operation.class.name.split('::').last.downcase
150
+ return op_name if op_name
151
+
152
+ klass = operation.class
153
+ @operation_names_mutex.synchronize do
154
+ @operation_names[klass] ||= klass.name.split('::').last.downcase
155
+ end
125
156
  end
126
157
 
127
- # Builds span attributes for the operation.
158
+ # Builds the attributes passed at span creation. The cursor id is set
159
+ # by trace_with_span once the span is known to record.
160
+ # db.operation.summary is outside the spec's recommended creation set,
161
+ # but it is the span name, which is built anyway, and an attribute
162
+ # passed at creation is cheaper than one set later.
128
163
  #
129
164
  # @param operation [ Mongo::Operation ] the operation.
130
165
  # @param op_name [ String | nil ] optional operation name.
@@ -136,8 +171,7 @@ module Mongo
136
171
  'db.namespace' => operation.db_name.to_s,
137
172
  'db.collection.name' => collection_name(operation),
138
173
  'db.operation.name' => operation_name(operation, op_name),
139
- 'db.operation.summary' => operation_span_name(operation, op_name),
140
- 'db.mongodb.cursor_id' => operation.cursor_id,
174
+ 'db.operation.summary' => operation_span_name(operation, op_name)
141
175
  }.compact
142
176
  end
143
177
 
@@ -150,7 +184,8 @@ module Mongo
150
184
  # @param cursor_id [ Integer | nil ] the cursor ID before the operation.
151
185
  # @param context [ OpenTelemetry::Context ] the OpenTelemetry context.
152
186
  # @param span [ OpenTelemetry::Trace::Span ] the current span.
153
- def process_cursor_context(result, cursor_id, context, span)
187
+ # @param recording [ true | false ] whether the span is recording.
188
+ def process_cursor_context(result, cursor_id, context, span, recording)
154
189
  return unless result.is_a?(Cursor)
155
190
 
156
191
  if result.id.zero?
@@ -159,7 +194,7 @@ module Mongo
159
194
  elsif result.id && cursor_id.nil?
160
195
  # New cursor created, store its context.
161
196
  cursor_context_map[result.id] = context
162
- span.set_attribute('db.mongodb.cursor_id', result.id)
197
+ span.set_attribute('db.mongodb.cursor_id', result.id) if recording
163
198
  end
164
199
  end
165
200
 
@@ -200,6 +200,9 @@ module Mongo
200
200
  # Generates a unique key for transaction tracking.
201
201
  #
202
202
  # Returns nil for implicit sessions or sessions not in a transaction.
203
+ # Every operation in a transaction needs the key, so it is cached on
204
+ # the session together with the transaction number it was built for.
205
+ # Two threads may build it at the same time; they build the same key.
203
206
  #
204
207
  # @param session [ Mongo::Session ] the session.
205
208
  #
@@ -207,7 +210,13 @@ module Mongo
207
210
  def transaction_map_key(session)
208
211
  return if session.nil? || session.implicit? || !session.in_transaction?
209
212
 
210
- "#{session.session_id['id'].to_uuid}-#{session.txn_num}"
213
+ txn_num = session.txn_num
214
+ cached = session.tracing_transaction_key
215
+ return cached[1] if cached && cached[0] == txn_num
216
+
217
+ key = "#{session.session_id['id'].to_uuid}-#{txn_num}"
218
+ session.tracing_transaction_key = [ txn_num, key ].freeze
219
+ key
211
220
  end
212
221
 
213
222
  private
@@ -614,7 +614,7 @@ module Mongo
614
614
  #
615
615
  # @return [ Hash | nil ] The auth mechanism properties hash.
616
616
  def convert_auth_mech_props(_name, value)
617
- properties = hash_extractor('authMechanismProperties', value)
617
+ properties = hash_extractor('authMechanismProperties', value, invalidate_on_error: true)
618
618
  if properties
619
619
  properties.each do |k, v|
620
620
  properties[k] = (v.downcase == 'true') if k.to_s.downcase == 'canonicalize_host_name' && v
@@ -863,16 +863,27 @@ module Mongo
863
863
 
864
864
  # Extract values from the string and put them into a nested hash.
865
865
  #
866
+ # Multiple key-value pairs are delimited by a comma. Within each pair,
867
+ # the key is everything up to the first colon and the value is everything
868
+ # after it, so values may themselves contain colons (for example
869
+ # TOKEN_RESOURCE:mongodb://host).
870
+ #
866
871
  # @param [ String ] name Name of the URI option being processed.
867
872
  # @param [ String ] value The string to build a hash from.
873
+ # @param [ true | false ] invalidate_on_error When a pair is malformed
874
+ # (has no value), discard the whole option and return nil instead of
875
+ # skipping just that pair. Used for options such as
876
+ # authMechanismProperties whose values must not contain commas.
868
877
  #
869
- # @return [ Hash ] The hash built from the string.
870
- def hash_extractor(name, value)
878
+ # @return [ Hash | nil ] The hash built from the string, or nil.
879
+ def hash_extractor(name, value, invalidate_on_error: false)
871
880
  h = {}
872
881
  value.split(',').each do |tag|
873
- k, v = tag.split(':')
882
+ k, v = tag.split(':', 2)
874
883
  if v.nil?
875
884
  log_warn("Invalid hash value for #{name}: key `#{k}` does not have a value: #{value}")
885
+ return nil if invalidate_on_error
886
+
876
887
  next
877
888
  end
878
889
 
data/lib/mongo/version.rb CHANGED
@@ -5,5 +5,5 @@ module Mongo
5
5
  #
6
6
  # Note that this file is automatically updated via `rake candidate:create`.
7
7
  # Manual changes to this file will be overwritten by that rake task.
8
- VERSION = '2.25.0'
8
+ VERSION = '2.26.1'
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.25.0
4
+ version: 2.26.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - The MongoDB Ruby Team
@@ -561,7 +561,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
561
561
  - !ruby/object:Gem::Version
562
562
  version: '0'
563
563
  requirements: []
564
- rubygems_version: 4.0.17
564
+ rubygems_version: 4.0.23
565
565
  specification_version: 4
566
566
  summary: Ruby driver for MongoDB
567
567
  test_files: []