mongo 2.9.2 → 2.10.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
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