mongo 2.9.2 → 2.10.0.rc0

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 (227) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongo.rb +1 -0
  5. data/lib/mongo/auth/user/view.rb +4 -4
  6. data/lib/mongo/bulk_write.rb +14 -8
  7. data/lib/mongo/bulk_write/result.rb +1 -1
  8. data/lib/mongo/bulk_write/result_combiner.rb +2 -2
  9. data/lib/mongo/bulk_write/transformable.rb +17 -9
  10. data/lib/mongo/client.rb +107 -16
  11. data/lib/mongo/cluster.rb +47 -25
  12. data/lib/mongo/cluster/topology/replica_set_no_primary.rb +1 -1
  13. data/lib/mongo/cluster_time.rb +139 -0
  14. data/lib/mongo/collection.rb +84 -25
  15. data/lib/mongo/collection/view.rb +7 -3
  16. data/lib/mongo/collection/view/aggregation.rb +4 -4
  17. data/lib/mongo/collection/view/builder/aggregation.rb +31 -6
  18. data/lib/mongo/collection/view/builder/find_command.rb +4 -1
  19. data/lib/mongo/collection/view/builder/map_reduce.rb +4 -1
  20. data/lib/mongo/collection/view/change_stream.rb +54 -66
  21. data/lib/mongo/collection/view/iterable.rb +2 -2
  22. data/lib/mongo/collection/view/map_reduce.rb +6 -4
  23. data/lib/mongo/collection/view/readable.rb +36 -16
  24. data/lib/mongo/collection/view/writable.rb +68 -22
  25. data/lib/mongo/cursor.rb +87 -20
  26. data/lib/mongo/database.rb +47 -43
  27. data/lib/mongo/database/view.rb +54 -11
  28. data/lib/mongo/error.rb +13 -4
  29. data/lib/mongo/error/invalid_write_concern.rb +2 -2
  30. data/lib/mongo/error/operation_failure.rb +65 -11
  31. data/lib/mongo/error/parser.rb +41 -8
  32. data/lib/mongo/grid/fs_bucket.rb +26 -6
  33. data/lib/mongo/grid/stream/read.rb +9 -2
  34. data/lib/mongo/grid/stream/write.rb +21 -5
  35. data/lib/mongo/index/view.rb +3 -3
  36. data/lib/mongo/lint.rb +10 -3
  37. data/lib/mongo/operation.rb +2 -0
  38. data/lib/mongo/operation/aggregate/result.rb +19 -6
  39. data/lib/mongo/operation/collections_info.rb +1 -1
  40. data/lib/mongo/operation/get_more/result.rb +9 -0
  41. data/lib/mongo/operation/list_collections/command.rb +1 -3
  42. data/lib/mongo/operation/list_collections/op_msg.rb +1 -2
  43. data/lib/mongo/operation/parallel_scan/command.rb +4 -1
  44. data/lib/mongo/operation/parallel_scan/op_msg.rb +4 -1
  45. data/lib/mongo/operation/result.rb +27 -4
  46. data/lib/mongo/operation/shared/executable.rb +19 -5
  47. data/lib/mongo/operation/shared/executable_no_validate.rb +1 -2
  48. data/lib/mongo/operation/shared/executable_transaction_label.rb +0 -9
  49. data/lib/mongo/operation/shared/polymorphic_result.rb +9 -1
  50. data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
  51. data/lib/mongo/operation/shared/sessions_supported.rb +42 -32
  52. data/lib/mongo/operation/shared/specifiable.rb +40 -0
  53. data/lib/mongo/operation/shared/unpinnable.rb +39 -0
  54. data/lib/mongo/operation/shared/write.rb +1 -1
  55. data/lib/mongo/protocol/update.rb +6 -2
  56. data/lib/mongo/retryable.rb +79 -39
  57. data/lib/mongo/server/connection.rb +10 -3
  58. data/lib/mongo/server/description.rb +25 -1
  59. data/lib/mongo/server/monitor/connection.rb +1 -1
  60. data/lib/mongo/server_selector.rb +10 -0
  61. data/lib/mongo/server_selector/selectable.rb +172 -32
  62. data/lib/mongo/session.rb +654 -581
  63. data/lib/mongo/session/session_pool.rb +1 -1
  64. data/lib/mongo/socket.rb +7 -28
  65. data/lib/mongo/socket/ssl.rb +26 -1
  66. data/lib/mongo/socket/tcp.rb +3 -0
  67. data/lib/mongo/socket/unix.rb +3 -0
  68. data/lib/mongo/uri.rb +112 -265
  69. data/lib/mongo/uri/srv_protocol.rb +4 -1
  70. data/lib/mongo/version.rb +1 -1
  71. data/lib/mongo/write_concern.rb +10 -29
  72. data/lib/mongo/write_concern/acknowledged.rb +12 -0
  73. data/lib/mongo/write_concern/base.rb +17 -13
  74. data/lib/mongo/write_concern/unacknowledged.rb +12 -0
  75. data/spec/atlas/atlas_connectivity_spec.rb +7 -37
  76. data/spec/atlas/operations_spec.rb +25 -0
  77. data/spec/integration/change_stream_examples_spec.rb +45 -31
  78. data/spec/integration/change_stream_spec.rb +305 -5
  79. data/spec/integration/client_spec.rb +44 -0
  80. data/spec/integration/command_monitoring_spec.rb +1 -0
  81. data/spec/integration/command_spec.rb +7 -1
  82. data/spec/integration/mmapv1_spec.rb +28 -0
  83. data/spec/integration/mongos_pinning_spec.rb +34 -0
  84. data/spec/integration/operation_failure_code_spec.rb +2 -2
  85. data/spec/integration/{read_concern.rb → read_concern_spec.rb} +7 -1
  86. data/spec/integration/read_preference_spec.rb +485 -0
  87. data/spec/integration/retryable_writes_spec.rb +8 -19
  88. data/spec/integration/sdam_error_handling_spec.rb +1 -1
  89. data/spec/integration/sdam_events_spec.rb +2 -2
  90. data/spec/integration/server_description_spec.rb +14 -17
  91. data/spec/integration/server_selector_spec.rb +7 -3
  92. data/spec/integration/server_spec.rb +48 -0
  93. data/spec/integration/ssl_uri_options_spec.rb +1 -1
  94. data/spec/integration/step_down_spec.rb +10 -4
  95. data/spec/integration/transactions_examples_spec.rb +11 -10
  96. data/spec/lite_spec_helper.rb +19 -16
  97. data/spec/mongo/auth/scram/negotiation_spec.rb +11 -8
  98. data/spec/mongo/bulk_write/ordered_combiner_spec.rb +6 -6
  99. data/spec/mongo/bulk_write/unordered_combiner_spec.rb +4 -4
  100. data/spec/mongo/bulk_write_spec.rb +12 -2
  101. data/spec/mongo/client_construction_spec.rb +160 -8
  102. data/spec/mongo/client_spec.rb +5 -4
  103. data/spec/mongo/cluster_spec.rb +6 -6
  104. data/spec/mongo/cluster_time_spec.rb +148 -0
  105. data/spec/mongo/collection/view/aggregation_spec.rb +34 -15
  106. data/spec/mongo/collection/view/change_stream_spec.rb +62 -3
  107. data/spec/mongo/collection/view/map_reduce_spec.rb +7 -5
  108. data/spec/mongo/collection/view/readable_spec.rb +4 -4
  109. data/spec/mongo/collection_spec.rb +331 -14
  110. data/spec/mongo/cursor_spec.rb +117 -5
  111. data/spec/mongo/database_spec.rb +240 -8
  112. data/spec/mongo/error/operation_failure_spec.rb +47 -1
  113. data/spec/mongo/error/parser_spec.rb +160 -23
  114. data/spec/mongo/operation/insert/bulk_spec.rb +2 -1
  115. data/spec/mongo/operation/result_spec.rb +27 -0
  116. data/spec/mongo/operation/update/bulk_spec.rb +1 -0
  117. data/spec/mongo/retryable_spec.rb +2 -0
  118. data/spec/mongo/server/app_metadata_spec.rb +2 -2
  119. data/spec/mongo/server/connection_spec.rb +13 -17
  120. data/spec/mongo/server/monitor/connection_spec.rb +13 -10
  121. data/spec/mongo/server_selector_spec.rb +34 -2
  122. data/spec/mongo/session/session_pool_spec.rb +14 -3
  123. data/spec/mongo/session_spec.rb +3 -3
  124. data/spec/mongo/session_transaction_spec.rb +4 -3
  125. data/spec/mongo/socket/ssl_spec.rb +19 -5
  126. data/spec/mongo/socket_spec.rb +1 -62
  127. data/spec/mongo/uri/srv_protocol_spec.rb +14 -20
  128. data/spec/mongo/uri_option_parsing_spec.rb +94 -8
  129. data/spec/mongo/uri_spec.rb +23 -10
  130. data/spec/mongo/write_concern_spec.rb +56 -3
  131. data/spec/spec_tests/change_streams_spec.rb +2 -1
  132. data/spec/spec_tests/cmap_spec.rb +1 -1
  133. data/spec/spec_tests/crud_spec.rb +12 -2
  134. data/spec/spec_tests/data/change_streams/change-streams-errors.yml +24 -1
  135. data/spec/spec_tests/data/change_streams/change-streams.yml +172 -3
  136. data/spec/spec_tests/data/command_monitoring/bulkWrite.yml +1 -1
  137. data/spec/spec_tests/data/command_monitoring/updateMany.yml +0 -2
  138. data/spec/spec_tests/data/command_monitoring/updateOne.yml +0 -5
  139. data/spec/spec_tests/data/crud/read/aggregate-out.yml +0 -6
  140. data/spec/spec_tests/data/crud/read/count-empty.yml +29 -0
  141. data/spec/spec_tests/data/crud/write/bulkWrite-arrayFilters.yml +1 -0
  142. data/spec/spec_tests/data/crud/write/bulkWrite-collation.yml +101 -0
  143. data/spec/spec_tests/data/crud/write/bulkWrite.yml +401 -0
  144. data/spec/spec_tests/data/crud/write/insertMany.yml +58 -2
  145. data/spec/spec_tests/data/crud/write/updateMany-arrayFilters.yml +3 -0
  146. data/spec/spec_tests/data/crud/write/updateOne-arrayFilters.yml +6 -1
  147. data/spec/spec_tests/data/crud_v2/aggregate-merge.yml +103 -0
  148. data/spec/spec_tests/data/crud_v2/aggregate-out-readConcern.yml +110 -0
  149. data/spec/spec_tests/data/crud_v2/bulkWrite-arrayFilters.yml +81 -0
  150. data/spec/spec_tests/data/crud_v2/db-aggregate.yml +38 -0
  151. data/spec/spec_tests/data/crud_v2/updateWithPipelines.yml +92 -0
  152. data/spec/spec_tests/data/retryable_writes/insertOne-serverErrors.yml +2 -2
  153. data/spec/spec_tests/data/transactions/abort.yml +3 -0
  154. data/spec/spec_tests/data/transactions/bulk.yml +3 -8
  155. data/spec/spec_tests/data/transactions/causal-consistency.yml +3 -8
  156. data/spec/spec_tests/data/transactions/commit.yml +3 -1
  157. data/spec/spec_tests/data/transactions/count.yml +3 -0
  158. data/spec/spec_tests/data/transactions/delete.yml +3 -0
  159. data/spec/spec_tests/data/transactions/error-labels.yml +4 -1
  160. data/spec/spec_tests/data/transactions/errors-client.yml +56 -0
  161. data/spec/spec_tests/data/transactions/errors.yml +3 -0
  162. data/spec/spec_tests/data/transactions/findOneAndDelete.yml +3 -0
  163. data/spec/spec_tests/data/transactions/findOneAndReplace.yml +3 -0
  164. data/spec/spec_tests/data/transactions/findOneAndUpdate.yml +3 -0
  165. data/spec/spec_tests/data/transactions/insert.yml +3 -0
  166. data/spec/spec_tests/data/transactions/isolation.yml +3 -0
  167. data/spec/spec_tests/data/transactions/mongos-pin-auto.yml +1671 -0
  168. data/spec/spec_tests/data/transactions/mongos-recovery-token.yml +347 -0
  169. data/spec/spec_tests/data/transactions/pin-mongos.yml +557 -0
  170. data/spec/spec_tests/data/transactions/read-concern.yml +3 -0
  171. data/spec/spec_tests/data/transactions/read-pref.yml +3 -0
  172. data/spec/spec_tests/data/transactions/reads.yml +3 -0
  173. data/spec/spec_tests/data/transactions/retryable-abort.yml +5 -2
  174. data/spec/spec_tests/data/transactions/retryable-commit.yml +4 -1
  175. data/spec/spec_tests/data/transactions/retryable-writes.yml +3 -0
  176. data/spec/spec_tests/data/transactions/run-command.yml +3 -0
  177. data/spec/spec_tests/data/transactions/transaction-options.yml +6 -0
  178. data/spec/spec_tests/data/transactions/update.yml +3 -8
  179. data/spec/spec_tests/data/transactions/write-concern.yml +348 -38
  180. data/spec/spec_tests/data/transactions_api/callback-aborts.yml +6 -0
  181. data/spec/spec_tests/data/transactions_api/callback-commits.yml +5 -0
  182. data/spec/spec_tests/data/transactions_api/callback-retry.yml +7 -2
  183. data/spec/spec_tests/data/transactions_api/commit-retry.yml +70 -15
  184. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror-4.2.yml +3 -0
  185. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror.yml +3 -0
  186. data/spec/spec_tests/data/transactions_api/commit-writeconcernerror.yml +59 -109
  187. data/spec/spec_tests/data/transactions_api/commit.yml +5 -0
  188. data/spec/spec_tests/data/transactions_api/transaction-options.yml +10 -0
  189. data/spec/spec_tests/retryable_reads_spec.rb +5 -2
  190. data/spec/spec_tests/retryable_writes_spec.rb +5 -2
  191. data/spec/spec_tests/sdam_monitoring_spec.rb +3 -3
  192. data/spec/spec_tests/sdam_spec.rb +2 -2
  193. data/spec/spec_tests/transactions_api_spec.rb +1 -67
  194. data/spec/spec_tests/transactions_spec.rb +2 -66
  195. data/spec/support/authorization.rb +4 -0
  196. data/spec/support/change_streams.rb +30 -10
  197. data/spec/support/change_streams/operation.rb +27 -0
  198. data/spec/support/client_registry.rb +44 -25
  199. data/spec/support/cluster_config.rb +25 -14
  200. data/spec/support/cluster_tools.rb +32 -10
  201. data/spec/support/command_monitoring.rb +1 -1
  202. data/spec/support/common_shortcuts.rb +30 -0
  203. data/spec/support/connection_string.rb +8 -3
  204. data/spec/support/constraints.rb +34 -0
  205. data/spec/support/crud.rb +31 -16
  206. data/spec/support/crud/context.rb +23 -0
  207. data/spec/support/crud/operation.rb +311 -14
  208. data/spec/support/crud/spec.rb +2 -1
  209. data/spec/support/crud/test.rb +24 -27
  210. data/spec/support/crud/test_base.rb +22 -0
  211. data/spec/support/crud/verifier.rb +15 -1
  212. data/spec/support/event_subscriber.rb +12 -0
  213. data/spec/support/sdam_formatter_integration.rb +12 -6
  214. data/spec/support/shared/server_selector.rb +10 -0
  215. data/spec/support/shared/session.rb +13 -12
  216. data/spec/support/spec_config.rb +32 -22
  217. data/spec/support/spec_setup.rb +2 -2
  218. data/spec/support/transactions.rb +87 -0
  219. data/spec/support/transactions/context.rb +33 -0
  220. data/spec/support/transactions/operation.rb +99 -349
  221. data/spec/support/transactions/spec.rb +1 -3
  222. data/spec/support/transactions/test.rb +110 -49
  223. data/spec/support/utils.rb +74 -1
  224. metadata +52 -10
  225. metadata.gz.sig +0 -0
  226. data/spec/support/crud/read.rb +0 -265
  227. data/spec/support/crud/write.rb +0 -284
@@ -117,7 +117,7 @@ module Mongo
117
117
  :selector => {endSessions: @queue.shift(10_000).collect { |s| s.session_id }},
118
118
  :db_name => Database::ADMIN).execute(server)
119
119
  end
120
- rescue
120
+ rescue Mongo::Error
121
121
  end
122
122
 
123
123
  private
@@ -49,9 +49,6 @@ module Mongo
49
49
  # @return [ Hash ] The options.
50
50
  attr_reader :options
51
51
 
52
- # @return [ Float ] timeout The socket timeout.
53
- attr_reader :timeout
54
-
55
52
  # Is the socket connection alive?
56
53
  #
57
54
  # @example Is the socket alive?
@@ -126,14 +123,10 @@ module Mongo
126
123
  def read(length)
127
124
  handle_errors do
128
125
  data = read_from_socket(length)
129
- unless (data.length > 0 || length == 0)
130
- raise IOError, "Expected to read > 0 bytes but read 0 bytes"
131
- end
126
+ raise IOError unless (data.length > 0 || length == 0)
132
127
  while data.length < length
133
128
  chunk = read_from_socket(length - data.length)
134
- unless (chunk.length > 0 || length == 0)
135
- raise IOError, "Expected to read > 0 bytes but read 0 bytes"
136
- end
129
+ raise IOError unless (chunk.length > 0 || length == 0)
137
130
  data << chunk
138
131
  end
139
132
  data
@@ -192,9 +185,7 @@ module Mongo
192
185
  return ''.force_encoding('BINARY')
193
186
  end
194
187
 
195
- if _timeout = self.timeout
196
- deadline = Time.now + _timeout
197
- end
188
+ deadline = (Time.now + timeout) if timeout
198
189
 
199
190
  # We want to have a fixed and reasonably small size buffer for reads
200
191
  # because, for example, OpenSSL reads in 16 kb chunks max.
@@ -241,22 +232,10 @@ module Mongo
241
232
  data[retrieved, chunk.length] = chunk
242
233
  retrieved += chunk.length
243
234
  end
244
- # As explained in https://ruby-doc.com/core-trunk/IO.html#method-c-select,
245
- # reading from a TLS socket may require writing which may raise WaitWritable
246
- rescue IO::WaitReadable, IO::WaitWritable => exc
247
- if deadline
248
- select_timeout = deadline - Time.now
249
- if select_timeout <= 0
250
- raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data"
251
- end
252
- end
253
- if exc.is_a?(IO::WaitReadable)
254
- select_args = [[@socket], nil, [@socket], select_timeout]
255
- else
256
- select_args = [nil, [@socket], [@socket], select_timeout]
257
- end
258
- unless Kernel::select(*select_args)
259
- raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data"
235
+ rescue IO::WaitReadable
236
+ select_timeout = (deadline - Time.now) if deadline
237
+ if (select_timeout && select_timeout <= 0) || !Kernel::select([@socket], nil, [@socket], select_timeout)
238
+ raise Timeout::Error.new("Took more than #{timeout} seconds to receive data.")
260
239
  end
261
240
  retry
262
241
  end
@@ -33,6 +33,9 @@ module Mongo
33
33
  # @return [ Integer ] port The port to connect to.
34
34
  attr_reader :port
35
35
 
36
+ # @return [ Float ] timeout The socket timeout.
37
+ attr_reader :timeout
38
+
36
39
  # Establishes a socket connection.
37
40
  #
38
41
  # @example Connect the socket.
@@ -125,10 +128,32 @@ module Mongo
125
128
 
126
129
  def create_context(options)
127
130
  OpenSSL::SSL::SSLContext.new.tap do |context|
131
+ if OpenSSL::SSL.const_defined?(:OP_NO_RENEGOTIATION)
132
+ context.options = context.options | OpenSSL::SSL::OP_NO_RENEGOTIATION
133
+ end
134
+
135
+ if context.respond_to?(:renegotiation_cb=)
136
+ # Disable renegotiation for older Ruby versions per the sample code at
137
+ # https://rubydocs.org/d/ruby-2-6-0/classes/OpenSSL/SSL/SSLContext.html
138
+ # In JRuby we must allow one call as this callback is invoked for
139
+ # the initial connection also, not just for renegotiations -
140
+ # https://github.com/jruby/jruby-openssl/issues/180
141
+ if BSON::Environment.jruby?
142
+ allowed_calls = 1
143
+ else
144
+ allowed_calls = 0
145
+ end
146
+ context.renegotiation_cb = lambda do |ssl|
147
+ if allowed_calls <= 0
148
+ raise RuntimeError, 'Client renegotiation disabled'
149
+ end
150
+ allowed_calls -= 1
151
+ end
152
+ end
153
+
128
154
  set_cert(context, options)
129
155
  set_key(context, options)
130
156
 
131
-
132
157
  if verify_certificate?
133
158
  context.verify_mode = OpenSSL::SSL::VERIFY_PEER
134
159
  set_cert_verification(context, options)
@@ -26,6 +26,9 @@ module Mongo
26
26
  # @return [ Integer ] port The port to connect to.
27
27
  attr_reader :port
28
28
 
29
+ # @return [ Float ] timeout The socket timeout.
30
+ attr_reader :timeout
31
+
29
32
  # Establishes a socket connection.
30
33
  #
31
34
  # @example Connect the socket.
@@ -23,6 +23,9 @@ module Mongo
23
23
  # @return [ String ] path The path to connect to.
24
24
  attr_reader :path
25
25
 
26
+ # @return [ Float ] timeout The socket timeout.
27
+ attr_reader :timeout
28
+
26
29
  # Initializes a new Unix socket.
27
30
  #
28
31
  # @example Create the Unix socket.
@@ -115,6 +115,7 @@ module Mongo
115
115
  # The character delimiting multiple options.
116
116
  #
117
117
  # @since 2.1.0
118
+ # @deprecated
118
119
  INDIV_URI_OPTS_DELIM = '&'.freeze
119
120
 
120
121
  # The character delimiting an option and its value.
@@ -188,21 +189,21 @@ module Mongo
188
189
  #
189
190
  # @since 2.0.0
190
191
  AUTH_MECH_MAP = {
191
- 'PLAIN' => :plain,
192
+ 'GSSAPI' => :gssapi,
192
193
  # MONGODB-CR is deprecated and will be removed in driver version 3.0
193
194
  'MONGODB-CR' => :mongodb_cr,
194
- 'GSSAPI' => :gssapi,
195
195
  'MONGODB-X509' => :mongodb_x509,
196
+ 'PLAIN' => :plain,
196
197
  'SCRAM-SHA-1' => :scram,
197
198
  'SCRAM-SHA-256' => :scram256
198
199
  }.freeze
199
200
 
200
201
  # Options that are allowed to appear more than once in the uri.
201
202
  #
202
- # In order to follow the URI options spec requirement that all instances of 'tls' and 'ssl' have
203
- # the same value, we need to keep track of all of the values passed in for those options.
204
- # Assuming they don't conflict, they will be condensed to a single value immediately after
205
- # parsing the URI.
203
+ # In order to follow the URI options spec requirement that all instances
204
+ # of 'tls' and 'ssl' have the same value, we need to keep track of all
205
+ # of the values passed in for those options. Assuming they don't conflict,
206
+ # they will be condensed to a single value immediately after parsing the URI.
206
207
  #
207
208
  # @since 2.1.0
208
209
  REPEATABLE_OPTIONS = [ :tag_sets, :ssl ]
@@ -349,7 +350,19 @@ module Mongo
349
350
  end
350
351
  end
351
352
 
352
- @servers = parse_servers!(hosts)
353
+ unless hosts.length > 0
354
+ raise_invalid_error!("Missing host; at least one must be provided")
355
+ end
356
+
357
+ @servers = hosts.split(',').map do |host|
358
+ if host.empty?
359
+ raise_invalid_error!('Empty host given in the host list')
360
+ end
361
+ decode(host).tap do |host|
362
+ validate_host!(host)
363
+ end
364
+ end
365
+
353
366
  @user = parse_user!(creds)
354
367
  @password = parse_password!(creds)
355
368
  @uri_options = Options::Redacted.new(parse_uri_options!(options))
@@ -368,20 +381,26 @@ module Mongo
368
381
  end
369
382
 
370
383
  def parse_uri_options!(string)
371
- return {} unless string
372
- string.split(INDIV_URI_OPTS_DELIM).reduce({}) do |uri_options, opt|
373
- key, value = opt.split('=', 2)
384
+ uri_options = {}
385
+ unless string
386
+ return uri_options
387
+ end
388
+ string.split('&').each do |option_str|
389
+ if option_str.empty?
390
+ next
391
+ end
392
+ key, value = option_str.split('=', 2)
374
393
  if value.nil?
375
394
  raise_invalid_error!("Option #{key} has no value")
376
395
  end
377
396
  if value.index('=')
378
397
  raise_invalid_error!("Value for option #{key} contains the key/value delimiter (=): #{value}")
379
398
  end
380
- key = ::URI.decode(key)
381
- value = ::URI.decode(value)
399
+ key = decode(key)
400
+ value = decode(value)
382
401
  add_uri_option(key, value, uri_options)
383
- uri_options
384
402
  end
403
+ uri_options
385
404
  end
386
405
 
387
406
  def parse_user!(string)
@@ -421,23 +440,44 @@ module Mongo
421
440
  end
422
441
  end
423
442
 
424
- def parse_servers!(string)
425
- raise_invalid_error!(INVALID_HOST) unless string.size > 0
426
- string.split(HOST_DELIM).reduce([]) do |servers, host|
427
- if host[0] == '['
428
- if host.index(']:')
429
- h, p = host.split(']:')
430
- validate_port_string!(p)
431
- end
432
- elsif host.index(HOST_PORT_DELIM)
433
- h, _, p = host.partition(HOST_PORT_DELIM)
434
- raise_invalid_error!(INVALID_HOST) unless h.size > 0
435
- validate_port_string!(p)
436
- elsif host =~ UNIX_SOCKET
437
- raise_invalid_error!(UNESCAPED_UNIX_SOCKET) if host =~ UNSAFE
438
- host = decode(host)
443
+ # Takes a host in ipv4/ipv6/hostname/socket path format and validates
444
+ # its format.
445
+ def validate_host!(host)
446
+ case host
447
+ when /\A\[[\d:]+\](?::(\d+))?\z/
448
+ # ipv6 with optional port
449
+ if port_str = $1
450
+ validate_port_string!(port_str)
439
451
  end
440
- servers << host
452
+ when /\A\//, /\.sock\z/
453
+ # Unix socket path.
454
+ # Spec requires us to validate that the path has no unescaped
455
+ # slashes, but if this were to be the case, parsing would have
456
+ # already failed elsewhere because the URI would've been split in
457
+ # a weird place.
458
+ # The spec also allows relative socket paths and requires that
459
+ # socket paths end in ".sock". We accept all paths but special case
460
+ # the .sock extension to avoid relative paths falling into the
461
+ # host:port case below.
462
+ when /[\/\[\]]/
463
+ # Not a host:port nor an ipv4 address with optional port.
464
+ # Possibly botched ipv6 address with e.g. port delimiter present and
465
+ # port missing, or extra junk before or after.
466
+ raise_invalid_error!("Invalid hostname: #{host}")
467
+ when /:.*:/m
468
+ raise_invalid_error!("Multiple port delimiters are not allowed: #{host}")
469
+ else
470
+ # host:port or ipv4 address with optional port number
471
+ host, port = host.split(':')
472
+ if host.empty?
473
+ raise_invalid_error!("Host is empty: #{host}")
474
+ end
475
+
476
+ if port && port.empty?
477
+ raise_invalid_error!("Port is empty: #{port}")
478
+ end
479
+
480
+ validate_port_string!(port)
441
481
  end
442
482
  end
443
483
 
@@ -472,21 +512,21 @@ module Mongo
472
512
  end
473
513
 
474
514
  # Replica Set Options
475
- uri_option 'replicaset', :replica_set, :type => :replica_set
515
+ uri_option 'replicaset', :replica_set
476
516
 
477
517
  # Timeout Options
478
- uri_option 'connecttimeoutms', :connect_timeout, :type => :connect_timeout
479
- uri_option 'sockettimeoutms', :socket_timeout, :type => :socket_timeout
480
- uri_option 'serverselectiontimeoutms', :server_selection_timeout, :type => :server_selection_timeout
481
- uri_option 'localthresholdms', :local_threshold, :type => :local_threshold
482
- uri_option 'heartbeatfrequencyms', :heartbeat_frequency, :type => :heartbeat_frequency
483
- uri_option 'maxidletimems', :max_idle_time, :type => :max_idle_time
518
+ uri_option 'connecttimeoutms', :connect_timeout, :type => :ms
519
+ uri_option 'sockettimeoutms', :socket_timeout, :type => :ms
520
+ uri_option 'serverselectiontimeoutms', :server_selection_timeout, :type => :ms
521
+ uri_option 'localthresholdms', :local_threshold, :type => :ms
522
+ uri_option 'heartbeatfrequencyms', :heartbeat_frequency, :type => :ms
523
+ uri_option 'maxidletimems', :max_idle_time, :type => :ms
484
524
 
485
525
  # Write Options
486
- uri_option 'w', :w, :group => :write, type: :w
487
- uri_option 'journal', :j, :group => :write, :type => :journal
488
- uri_option 'fsync', :fsync, :group => :write, type: :bool
489
- uri_option 'wtimeoutms', :wtimeout, :group => :write, :type => :wtimeout
526
+ uri_option 'w', :w, :group => :write_concern, type: :w
527
+ uri_option 'journal', :j, :group => :write_concern, :type => :bool
528
+ uri_option 'fsync', :fsync, :group => :write_concern, type: :bool
529
+ uri_option 'wtimeoutms', :wtimeout, :group => :write_concern, :type => :integer
490
530
 
491
531
  # Read Options
492
532
  uri_option 'readpreference', :mode, :group => :read, :type => :read_mode
@@ -494,21 +534,21 @@ module Mongo
494
534
  uri_option 'maxstalenessseconds', :max_staleness, :group => :read, :type => :max_staleness
495
535
 
496
536
  # Pool options
497
- uri_option 'minpoolsize', :min_pool_size, :type => :min_pool_size
498
- uri_option 'maxpoolsize', :max_pool_size, :type => :max_pool_size
499
- uri_option 'waitqueuetimeoutms', :wait_queue_timeout, :type => :wait_queue_timeout
537
+ uri_option 'minpoolsize', :min_pool_size, :type => :integer
538
+ uri_option 'maxpoolsize', :max_pool_size, :type => :integer
539
+ uri_option 'waitqueuetimeoutms', :wait_queue_timeout, :type => :ms
500
540
 
501
541
  # Security Options
502
- uri_option 'ssl', :ssl, :type => :ssl
503
- uri_option 'tls', :ssl, :type => :tls
542
+ uri_option 'ssl', :ssl, :type => :repeated_bool
543
+ uri_option 'tls', :ssl, :type => :repeated_bool
504
544
  uri_option 'tlsallowinvalidcertificates', :ssl_verify_certificate,
505
- :type => :ssl_verify_certificate
545
+ :type => :inverse_bool
506
546
  uri_option 'tlsallowinvalidhostnames', :ssl_verify_hostname,
507
- :type => :ssl_verify_hostname
547
+ :type => :inverse_bool
508
548
  uri_option 'tlscafile', :ssl_ca_cert
509
549
  uri_option 'tlscertificatekeyfile', :ssl_cert
510
550
  uri_option 'tlscertificatekeyfilepassword', :ssl_key_pass_phrase
511
- uri_option 'tlsinsecure', :ssl_verify, :type => :ssl_verify
551
+ uri_option 'tlsinsecure', :ssl_verify, :type => :inverse_bool
512
552
 
513
553
  # Topology options
514
554
  uri_option 'connect', :connect, type: :symbol
@@ -521,9 +561,9 @@ module Mongo
521
561
  # Client Options
522
562
  uri_option 'appname', :app_name
523
563
  uri_option 'compressors', :compressors, :type => :array
524
- uri_option 'readconcernlevel', :level, group: :read_concern
525
- uri_option 'retryreads', :retry_reads, :type => :retry_reads
526
- uri_option 'retrywrites', :retry_writes, :type => :retry_writes
564
+ uri_option 'readconcernlevel', :level, group: :read_concern, type: :symbol
565
+ uri_option 'retryreads', :retry_reads, :type => :bool
566
+ uri_option 'retrywrites', :retry_writes, :type => :bool
527
567
  uri_option 'zlibcompressionlevel', :zlib_compression_level, :type => :zlib_compression_level
528
568
 
529
569
  # Applies URI value transformation by either using the default cast
@@ -602,15 +642,6 @@ module Mongo
602
642
  merge_uri_option(target, value, strategy[:name])
603
643
  end
604
644
 
605
- # Replica set transformation, avoid converting to Symbol.
606
- #
607
- # @param value [String] Replica set name.
608
- #
609
- # @return [String] Same value to avoid cast to Symbol.
610
- def replica_set(value)
611
- decode(value)
612
- end
613
-
614
645
  # Auth source transformation, either db string or :external.
615
646
  #
616
647
  # @param value [String] Authentication source.
@@ -618,7 +649,7 @@ module Mongo
618
649
  # @return [String] If auth source is database name.
619
650
  # @return [:external] If auth source is external authentication.
620
651
  def auth_source(value)
621
- value == '$external' ? :external : decode(value)
652
+ value == '$external' ? :external : value
622
653
  end
623
654
 
624
655
  # Authentication mechanism transformation.
@@ -627,7 +658,7 @@ module Mongo
627
658
  #
628
659
  # @return [Symbol] The transformed authentication mechanism.
629
660
  def auth_mech(value)
630
- AUTH_MECH_MAP[value.upcase].tap do |mech|
661
+ (AUTH_MECH_MAP[value.upcase] || value).tap do |mech|
631
662
  log_warn("#{value} is not a valid auth mechanism") unless mech
632
663
  end
633
664
  end
@@ -638,7 +669,7 @@ module Mongo
638
669
  #
639
670
  # @return [Symbol] The read mode symbol.
640
671
  def read_mode(value)
641
- READ_MODE_MAP[value.downcase]
672
+ READ_MODE_MAP[value.downcase] || value
642
673
  end
643
674
 
644
675
  # Read preference tags transformation.
@@ -668,7 +699,7 @@ module Mongo
668
699
  properties = hash_extractor('authMechanismProperties', value)
669
700
  if properties[:canonicalize_host_name]
670
701
  properties.merge!(canonicalize_host_name:
671
- %w(true TRUE).include?(properties[:canonicalize_host_name]))
702
+ properties[:canonicalize_host_name].downcase == 'true')
672
703
  end
673
704
  properties
674
705
  end
@@ -692,118 +723,15 @@ module Mongo
692
723
  nil
693
724
  end
694
725
 
695
- # Parses the max pool size.
696
- #
697
- # @param value [ String ] The max pool size string.
698
- #
699
- # @return [ Integer | nil ] The min pool size if it is valid, otherwise nil (and a warning will)
700
- # be logged.
701
- def max_pool_size(value)
702
- if /\A\d+\z/ =~ value
703
- return value.to_i
704
- end
705
-
706
- log_warn("#{value} is not a valid maxPoolSize")
707
- nil
708
- end
709
-
710
-
711
- # Parses the min pool size.
712
- #
713
- # @param value [ String ] The min pool size string.
714
- #
715
- # @return [ Integer | nil ] The min pool size if it is valid, otherwise nil (and a warning will
716
- # be logged).
717
- def min_pool_size(value)
718
- if /\A\d+\z/ =~ value
719
- return value.to_i
720
- end
721
-
722
- log_warn("#{value} is not a valid minPoolSize")
723
- nil
724
- end
725
-
726
- # Parses the journal value.
727
- #
728
- # @param value [ String ] The journal value.
729
- #
730
- # @return [ true | false | nil ] The journal value parsed out, otherwise nil (and a warning
731
- # will be logged).
732
- def journal(value)
733
- convert_bool('journal', value)
734
- end
735
-
736
- # Parses the ssl value from the URI.
737
- #
738
- # @param value [ String ] The ssl value.
739
- #
740
- # @return [ Array<true | false> ] The ssl value parsed out (stored in an array to facilitate
741
- # keeping track of all values).
742
- def ssl(value)
743
- [convert_bool('ssl', value)]
744
- end
745
-
746
- # Parses the tls value from the URI.
747
- #
748
- # @param value [ String ] The tls value.
749
- #
750
- # @return [ Array<true | false> ] The tls value parsed out (stored in an array to facilitate
751
- # keeping track of all values).
752
- def tls(value)
753
- [convert_bool('tls', value)]
754
- end
755
-
756
- # Parses the ssl_verify value from the tlsInsecure URI value. Note that this will be the inverse
757
- # of the value of tlsInsecure (if present).
758
- #
759
- # @param value [ String ] The tlsInsecure value.
760
- #
761
- # @return [ true | false | nil ] The ssl_verify value parsed out, otherwise nil (and a warning
762
- # will be logged).
763
- def ssl_verify(value)
764
- inverse_bool('tlsAllowInvalidCertificates', value)
765
- end
766
-
767
- # Parses the ssl_verify_certificate value from the tlsAllowInvalidCertificates URI value. Note
768
- # that this will be the inverse of the value of tlsInsecure (if present).
769
- #
770
- # @param value [ String ] The tlsAllowInvalidCertificates value.
771
- #
772
- # @return [ true | false | nil ] The ssl_verify_certificate value parsed out, otherwise nil
773
- # (and a warning will be logged).
774
- def ssl_verify_certificate(value)
775
- inverse_bool('tlsAllowInvalidCertificates', value)
776
- end
777
-
778
- # Parses the ssl_verify_hostname value from the tlsAllowInvalidHostnames URI value. Note that
779
- # this will be the inverse of the value of tlsAllowInvalidHostnames (if present).
780
- #
781
- # @param value [ String ] The tlsAllowInvalidHostnames value.
782
- #
783
- # @return [ true | false | nil ] The ssl_verify_hostname value parsed out, otherwise nil
784
- # (and a warning will be logged).
785
- def ssl_verify_hostname(value)
786
- inverse_bool('tlsAllowInvalidHostnames', value)
787
- end
788
-
789
- # Parses the retryReads value.
726
+ # Converts the value into a boolean and returns it wrapped in an array.
790
727
  #
791
- # @param value [ String ] The retryReads value.
792
- #
793
- # @return [ true | false | nil ] The boolean value parsed out, otherwise nil (and a warning
794
- # will be logged).
795
- def retry_reads(value)
796
- convert_bool('retryReads', value)
797
- end
798
-
799
- # Parses the retryWrites value.
800
- #
801
- # @param value [ String ] The retryWrites value.
728
+ # @param name [ String ] Name of the URI option being processed.
729
+ # @param value [ String ] URI option value.
802
730
  #
803
- # @return [ true | false | nil ] The boolean value parsed out, otherwise nil (and a warning
804
- # will be logged).
805
- def retry_writes(value)
806
- convert_bool('retryWrites', value)
731
+ # @return [ Array<true | false> ] The boolean value parsed and wraped
732
+ # in an array.
733
+ def convert_repeated_bool(name, value)
734
+ [convert_bool(name, value)]
807
735
  end
808
736
 
809
737
  # Converts +value+ into an integer.
@@ -880,7 +808,7 @@ module Mongo
880
808
  #
881
809
  # @return [ true | false | nil ] The inverse of the boolean value parsed out, otherwise nil
882
810
  # (and a warning will be logged).
883
- def inverse_bool(name, value)
811
+ def convert_inverse_bool(name, value)
884
812
  b = convert_bool(name, value)
885
813
 
886
814
  if b.nil?
@@ -897,11 +825,15 @@ module Mongo
897
825
  # @return [ Integer | nil ] The max staleness integer parsed out if it is valid, otherwise nil
898
826
  # (and a warning will be logged).
899
827
  def max_staleness(value)
900
- if /\A\d+\z/ =~ value
828
+ if /\A-?\d+\z/ =~ value
901
829
  int = value.to_i
902
830
 
903
- if int >= 0 && int < 90
904
- log_warn("max staleness must be either 0 or greater than 90: #{value}")
831
+ if int == -1
832
+ int = nil
833
+ end
834
+
835
+ if int && (int >= 0 && int < 90 || int < 0)
836
+ log_warn("max staleness should be either 0 or greater than 90: #{value}")
905
837
  end
906
838
 
907
839
  return int
@@ -911,91 +843,6 @@ module Mongo
911
843
  nil
912
844
  end
913
845
 
914
- # Parses the connectTimeoutMS value.
915
- #
916
- # @param value [ String ] The connectTimeoutMS value.
917
- #
918
- # @return [ Integer | nil ] The integer parsed out, otherwise nil (and a warning will be
919
- # logged).
920
- def connect_timeout(value)
921
- ms_convert('connectTimeoutMS', value)
922
- end
923
-
924
- # Parses the localThresholdMS value.
925
- #
926
- # @param value [ String ] The localThresholdMS value.
927
- #
928
- # @return [ Integer | nil ] The integer parsed out, otherwise nil (and a warning will be
929
- # logged).
930
- def local_threshold(value)
931
- ms_convert('localThresholdMS', value)
932
- end
933
-
934
- # Parses the heartbeatFrequencyMS value.
935
- #
936
- # @param value [ String ] The heartbeatFrequencyMS value.
937
- #
938
- # @return [ Integer | nil ] The integer parsed out, otherwise nil (and a warning will be
939
- # logged).
940
- def heartbeat_frequency(value)
941
- ms_convert('heartbeatFrequencyMS', value)
942
- end
943
-
944
- # Parses the maxIdleTimeMS value.
945
- #
946
- # @param value [ String ] The maxIdleTimeMS value.
947
- #
948
- # @return [ Integer | nil ] The integer parsed out, otherwise nil (and a warning will be
949
- # logged).
950
- def max_idle_time(value)
951
- ms_convert('maxIdleTimeMS', value)
952
- end
953
-
954
- # Parses the serverSelectionMS value.
955
- #
956
- # @param value [ String ] The serverSelectionMS value.
957
- #
958
- # @return [ Integer | nil ] The integer parsed out, otherwise nil (and a warning will be
959
- # logged).
960
- def server_selection_timeout(value)
961
- ms_convert('serverSelectionTimeoutMS', value)
962
- end
963
-
964
- # Parses the socketTimeoutMS value.
965
- #
966
- # @param value [ String ] The socketTimeoutMS value.
967
- #
968
- # @return [ Integer | nil ] The integer parsed out, otherwise nil (and a warning will be
969
- # logged).
970
- def socket_timeout(value)
971
- ms_convert('socketTimeoutMS', value)
972
- end
973
-
974
- # Parses the waitQueueTimeoutMS value.
975
- #
976
- # @param value [ String ] The waitQueueTimeoutMS value.
977
- #
978
- # @return [ Integer | nil ] The integer parsed out, otherwise nil (and a warning will be
979
- # logged).
980
- def wait_queue_timeout(value)
981
- ms_convert('MS', value)
982
- end
983
-
984
- # Parses the wtimeoutMS value.
985
- #
986
- # @param value [ String ] The wtimeoutMS value.
987
- #
988
- # @return [ Integer | nil ] The integer parsed out, otherwise nil (and a warning will be
989
- # logged).
990
- def wtimeout(value)
991
- unless /\A\d+\z/ =~ value
992
- log_warn("Invalid wtimeoutMS value: #{value}")
993
- return nil
994
- end
995
-
996
- value.to_i
997
- end
998
-
999
846
  # Ruby's convention is to provide timeouts in seconds, not milliseconds and
1000
847
  # to use fractions where more precision is necessary. The connection string
1001
848
  # options are always in MS so we provide an easy conversion type.
@@ -1005,7 +852,7 @@ module Mongo
1005
852
  # @return [ Float ] The seconds value.
1006
853
  #
1007
854
  # @since 2.0.0
1008
- def ms_convert(name, value)
855
+ def convert_ms(name, value)
1009
856
  unless /\A-?\d+(\.\d+)?\z/ =~ value
1010
857
  log_warn("Invalid ms value for #{name}: #{value}")
1011
858
  return nil
@@ -1032,7 +879,7 @@ module Mongo
1032
879
  return nil
1033
880
  end
1034
881
 
1035
- set.merge(decode(k).downcase.to_sym => decode(v))
882
+ set.merge(k.downcase.to_sym => v)
1036
883
  end
1037
884
  end
1038
885