mongo-lyon 1.2.4

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 (87) hide show
  1. data/LICENSE.txt +190 -0
  2. data/README.md +344 -0
  3. data/Rakefile +202 -0
  4. data/bin/mongo_console +34 -0
  5. data/docs/1.0_UPGRADE.md +21 -0
  6. data/docs/CREDITS.md +123 -0
  7. data/docs/FAQ.md +116 -0
  8. data/docs/GridFS.md +158 -0
  9. data/docs/HISTORY.md +225 -0
  10. data/docs/REPLICA_SETS.md +72 -0
  11. data/docs/TUTORIAL.md +247 -0
  12. data/docs/WRITE_CONCERN.md +28 -0
  13. data/lib/mongo.rb +77 -0
  14. data/lib/mongo/collection.rb +872 -0
  15. data/lib/mongo/connection.rb +875 -0
  16. data/lib/mongo/cursor.rb +449 -0
  17. data/lib/mongo/db.rb +607 -0
  18. data/lib/mongo/exceptions.rb +68 -0
  19. data/lib/mongo/gridfs/grid.rb +106 -0
  20. data/lib/mongo/gridfs/grid_ext.rb +57 -0
  21. data/lib/mongo/gridfs/grid_file_system.rb +145 -0
  22. data/lib/mongo/gridfs/grid_io.rb +394 -0
  23. data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
  24. data/lib/mongo/repl_set_connection.rb +342 -0
  25. data/lib/mongo/util/conversions.rb +89 -0
  26. data/lib/mongo/util/core_ext.rb +60 -0
  27. data/lib/mongo/util/pool.rb +185 -0
  28. data/lib/mongo/util/server_version.rb +71 -0
  29. data/lib/mongo/util/support.rb +82 -0
  30. data/lib/mongo/util/uri_parser.rb +181 -0
  31. data/lib/mongo/version.rb +3 -0
  32. data/mongo.gemspec +34 -0
  33. data/test/auxillary/1.4_features.rb +166 -0
  34. data/test/auxillary/authentication_test.rb +68 -0
  35. data/test/auxillary/autoreconnect_test.rb +41 -0
  36. data/test/auxillary/repl_set_auth_test.rb +58 -0
  37. data/test/auxillary/slave_connection_test.rb +36 -0
  38. data/test/auxillary/threaded_authentication_test.rb +101 -0
  39. data/test/bson/binary_test.rb +15 -0
  40. data/test/bson/bson_test.rb +614 -0
  41. data/test/bson/byte_buffer_test.rb +190 -0
  42. data/test/bson/hash_with_indifferent_access_test.rb +38 -0
  43. data/test/bson/json_test.rb +17 -0
  44. data/test/bson/object_id_test.rb +154 -0
  45. data/test/bson/ordered_hash_test.rb +197 -0
  46. data/test/collection_test.rb +893 -0
  47. data/test/connection_test.rb +303 -0
  48. data/test/conversions_test.rb +120 -0
  49. data/test/cursor_fail_test.rb +75 -0
  50. data/test/cursor_message_test.rb +43 -0
  51. data/test/cursor_test.rb +457 -0
  52. data/test/db_api_test.rb +715 -0
  53. data/test/db_connection_test.rb +15 -0
  54. data/test/db_test.rb +287 -0
  55. data/test/grid_file_system_test.rb +244 -0
  56. data/test/grid_io_test.rb +120 -0
  57. data/test/grid_test.rb +200 -0
  58. data/test/load/thin/load.rb +24 -0
  59. data/test/load/unicorn/load.rb +23 -0
  60. data/test/replica_sets/connect_test.rb +86 -0
  61. data/test/replica_sets/connection_string_test.rb +32 -0
  62. data/test/replica_sets/count_test.rb +35 -0
  63. data/test/replica_sets/insert_test.rb +53 -0
  64. data/test/replica_sets/pooled_insert_test.rb +55 -0
  65. data/test/replica_sets/query_secondaries.rb +96 -0
  66. data/test/replica_sets/query_test.rb +51 -0
  67. data/test/replica_sets/replication_ack_test.rb +66 -0
  68. data/test/replica_sets/rs_test_helper.rb +27 -0
  69. data/test/safe_test.rb +68 -0
  70. data/test/support/hash_with_indifferent_access.rb +199 -0
  71. data/test/support/keys.rb +45 -0
  72. data/test/support_test.rb +19 -0
  73. data/test/test_helper.rb +83 -0
  74. data/test/threading/threading_with_large_pool_test.rb +90 -0
  75. data/test/threading_test.rb +87 -0
  76. data/test/tools/auth_repl_set_manager.rb +14 -0
  77. data/test/tools/repl_set_manager.rb +266 -0
  78. data/test/unit/collection_test.rb +130 -0
  79. data/test/unit/connection_test.rb +98 -0
  80. data/test/unit/cursor_test.rb +99 -0
  81. data/test/unit/db_test.rb +96 -0
  82. data/test/unit/grid_test.rb +49 -0
  83. data/test/unit/pool_test.rb +9 -0
  84. data/test/unit/repl_set_connection_test.rb +72 -0
  85. data/test/unit/safe_test.rb +125 -0
  86. data/test/uri_test.rb +91 -0
  87. metadata +202 -0
@@ -0,0 +1,60 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ #:nodoc:
20
+ class Object
21
+
22
+ #:nodoc:
23
+ def tap
24
+ yield self
25
+ self
26
+ end unless respond_to? :tap
27
+
28
+ end
29
+
30
+ #:nodoc:
31
+ module Enumerable
32
+
33
+ #:nodoc:
34
+ def each_with_object(memo)
35
+ each { |element| yield(element, memo) }
36
+ memo
37
+ end unless [].respond_to?(:each_with_object)
38
+
39
+ end
40
+
41
+ #:nodoc:
42
+ class Hash
43
+
44
+ #:nodoc:
45
+ def assert_valid_keys(*valid_keys)
46
+ unknown_keys = keys - [valid_keys].flatten
47
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
48
+ end
49
+
50
+ end
51
+
52
+ #:nodoc:
53
+ class String
54
+
55
+ #:nodoc:
56
+ def to_bson_code
57
+ BSON::Code.new(self)
58
+ end
59
+
60
+ end
@@ -0,0 +1,185 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ module Mongo
19
+ class Pool
20
+
21
+ attr_accessor :host, :port, :size, :timeout, :safe, :checked_out, :unix_socket_path
22
+
23
+ # Create a new pool of connections.
24
+ #
25
+ def initialize(connection, host, port, opts={})
26
+ @connection = connection
27
+
28
+ @host, @port = host, port
29
+
30
+ unless @port
31
+ @unix_socket_path = host
32
+ @host = nil
33
+ end
34
+
35
+ # Pool size and timeout.
36
+ @size = opts[:size] || 1
37
+ @timeout = opts[:timeout] || 5.0
38
+
39
+ # Mutex for synchronizing pool access
40
+ @connection_mutex = Mutex.new
41
+
42
+ # Condition variable for signal and wait
43
+ @queue = ConditionVariable.new
44
+
45
+ # Operations to perform on a socket
46
+ @socket_ops = Hash.new { |h, k| h[k] = [] }
47
+
48
+ @sockets = []
49
+ @checked_out = []
50
+ end
51
+
52
+ def close
53
+ @sockets.each do |sock|
54
+ begin
55
+ sock.close
56
+ rescue IOError => ex
57
+ warn "IOError when attempting to close socket connected to #{self.to_s}: #{ex.inspect}"
58
+ end
59
+ end
60
+ @host = @port = @unix_socket_path = nil
61
+ @sockets.clear
62
+ @checked_out.clear
63
+ end
64
+
65
+ # Return a socket to the pool.
66
+ def checkin(socket)
67
+ @connection_mutex.synchronize do
68
+ @checked_out.delete(socket)
69
+ @queue.signal
70
+ end
71
+ true
72
+ end
73
+
74
+ # Adds a new socket to the pool and checks it out.
75
+ #
76
+ # This method is called exclusively from #checkout;
77
+ # therefore, it runs within a mutex.
78
+ def checkout_new_socket
79
+ begin
80
+ if @unix_socket_path
81
+ socket = UNIXSocket.new(@unix_socket_path)
82
+ else
83
+ socket = TCPSocket.new(@host, @port)
84
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
85
+ end
86
+ rescue => ex
87
+ raise ConnectionFailure, "Failed to connect to #{self.to_s}: #{ex}"
88
+ end
89
+
90
+ # If any saved authentications exist, we want to apply those
91
+ # when creating new sockets.
92
+ @connection.apply_saved_authentication(:socket => socket)
93
+
94
+ @sockets << socket
95
+ @checked_out << socket
96
+ socket
97
+ end
98
+
99
+ # If a user calls DB#authenticate, and several sockets exist,
100
+ # then we need a way to apply the authentication on each socket.
101
+ # So we store the apply_authentication method, and this will be
102
+ # applied right before the next use of each socket.
103
+ def authenticate_existing
104
+ @connection_mutex.synchronize do
105
+ @sockets.each do |socket|
106
+ @socket_ops[socket] << Proc.new do
107
+ @connection.apply_saved_authentication(:socket => socket)
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ # Store the logout op for each existing socket to be applied before
114
+ # the next use of each socket.
115
+ def logout_existing(db)
116
+ @connection_mutex.synchronize do
117
+ @sockets.each do |socket|
118
+ @socket_ops[socket] << Proc.new do
119
+ @connection.db(db).issue_logout(:socket => socket)
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ # Checks out the first available socket from the pool.
126
+ #
127
+ # This method is called exclusively from #checkout;
128
+ # therefore, it runs within a mutex.
129
+ def checkout_existing_socket
130
+ socket = (@sockets - @checked_out).first
131
+ @checked_out << socket
132
+ socket
133
+ end
134
+
135
+ # Check out an existing socket or create a new socket if the maximum
136
+ # pool size has not been exceeded. Otherwise, wait for the next
137
+ # available socket.
138
+ def checkout
139
+ @connection.connect if !@connection.connected?
140
+ start_time = Time.now
141
+ loop do
142
+ if (Time.now - start_time) > @timeout
143
+ raise ConnectionTimeoutError, "could not obtain connection within " +
144
+ "#{@timeout} seconds. The max pool size is currently #{@size}; " +
145
+ "consider increasing the pool size or timeout."
146
+ end
147
+
148
+ @connection_mutex.synchronize do
149
+ socket = if @checked_out.size < @sockets.size
150
+ checkout_existing_socket
151
+ elsif @sockets.size < @size
152
+ checkout_new_socket
153
+ end
154
+
155
+ if socket
156
+
157
+ # This calls all procs, in order, scoped to existing sockets.
158
+ # At the moment, we use this to lazily authenticate and
159
+ # logout existing socket connections.
160
+ @socket_ops[socket].reject! do |op|
161
+ op.call
162
+ end
163
+
164
+ return socket
165
+ else
166
+ # Otherwise, wait
167
+ if @logger
168
+ @logger.warn "MONGODB Waiting for available connection; " +
169
+ "#{@checked_out.size} of #{@size} connections checked out."
170
+ end
171
+ @queue.wait(@connection_mutex)
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ def to_s
178
+ if @unix_socket_path
179
+ "#{@unix_socket_path}"
180
+ else
181
+ "#{@host}:#{@port}"
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,71 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+ module Mongo
19
+ # Simple class for comparing server versions.
20
+ class ServerVersion
21
+ include Comparable
22
+
23
+ def initialize(version)
24
+ @version = version
25
+ end
26
+
27
+ # Implements comparable.
28
+ def <=>(new)
29
+ local, new = self.to_a, to_array(new)
30
+ for n in 0...local.size do
31
+ break if elements_include_mods?(local[n], new[n])
32
+ if local[n] < new[n].to_i
33
+ result = -1
34
+ break;
35
+ elsif local[n] > new[n].to_i
36
+ result = 1
37
+ break;
38
+ end
39
+ end
40
+ result || 0
41
+ end
42
+
43
+ # Return an array representation of this server version.
44
+ def to_a
45
+ to_array(@version)
46
+ end
47
+
48
+ # Return a string representation of this server version.
49
+ def to_s
50
+ @version
51
+ end
52
+
53
+ private
54
+
55
+ # Returns true if any elements include mod symbols (-, +)
56
+ def elements_include_mods?(*elements)
57
+ elements.any? { |n| n =~ /[\-\+]/ }
58
+ end
59
+
60
+ # Converts argument to an array of integers,
61
+ # appending any mods as the final element.
62
+ def to_array(version)
63
+ array = version.split(".").map {|n| (n =~ /^\d+$/) ? n.to_i : n }
64
+ if array.last =~ /(\d+)([\-\+])/
65
+ array[array.length-1] = $1.to_i
66
+ array << $2
67
+ end
68
+ array
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,82 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ require 'digest/md5'
20
+
21
+ module Mongo
22
+ module Support
23
+ include Mongo::Conversions
24
+ extend self
25
+
26
+ # Generate an MD5 for authentication.
27
+ #
28
+ # @param [String] username
29
+ # @param [String] password
30
+ # @param [String] nonce
31
+ #
32
+ # @return [String] a key for db authentication.
33
+ def auth_key(username, password, nonce)
34
+ Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
35
+ end
36
+
37
+ # Return a hashed password for auth.
38
+ #
39
+ # @param [String] username
40
+ # @param [String] plaintext
41
+ #
42
+ # @return [String]
43
+ def hash_password(username, plaintext)
44
+ Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
45
+ end
46
+
47
+
48
+ def validate_db_name(db_name)
49
+ unless [String, Symbol].include?(db_name.class)
50
+ raise TypeError, "db_name must be a string or symbol"
51
+ end
52
+
53
+ [" ", ".", "$", "/", "\\"].each do |invalid_char|
54
+ if db_name.include? invalid_char
55
+ raise Mongo::InvalidNSName, "database names cannot contain the character '#{invalid_char}'"
56
+ end
57
+ end
58
+ raise Mongo::InvalidNSName, "database name cannot be the empty string" if db_name.empty?
59
+ db_name
60
+ end
61
+
62
+ def format_order_clause(order)
63
+ case order
64
+ when String, Symbol then string_as_sort_parameters(order)
65
+ when Array then array_as_sort_parameters(order)
66
+ else
67
+ raise InvalidSortValueError, "Illegal sort clause, '#{order.class.name}'; must be of the form " +
68
+ "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
69
+ end
70
+ end
71
+
72
+ # Determine if a database command has succeeded by
73
+ # checking the document response.
74
+ #
75
+ # @param [Hash] doc
76
+ #
77
+ # @return [Boolean] true if the 'ok' key is either 1 or *true*.
78
+ def ok?(doc)
79
+ doc['ok'] == 1.0 || doc['ok'] == true
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,181 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ module Mongo
20
+ class URIParser
21
+
22
+ DEFAULT_PORT = 27017
23
+ MONGODB_URI_MATCHER = /(([-_.\w\d]+):([^@]+)@)?([-.\w\d]+)(:([\w\d]+))?(\/([-\d\w]+))?/
24
+ MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
25
+ SPEC_ATTRS = [:nodes, :auths]
26
+ OPT_ATTRS = [:connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync]
27
+
28
+ OPT_VALID = {:connect => lambda {|arg| ['direct', 'replicaset'].include?(arg)},
29
+ :replicaset => lambda {|arg| arg.length > 0},
30
+ :slaveok => lambda {|arg| ['true', 'false'].include?(arg)},
31
+ :safe => lambda {|arg| ['true', 'false'].include?(arg)},
32
+ :w => lambda {|arg| arg =~ /^\d+$/ },
33
+ :wtimeout => lambda {|arg| arg =~ /^\d+$/ },
34
+ :fsync => lambda {|arg| ['true', 'false'].include?(arg)}
35
+ }
36
+
37
+ OPT_ERR = {:connect => "must be 'direct' or 'replicaset'",
38
+ :replicaset => "must be a string containing the name of the replica set to connect to",
39
+ :slaveok => "must be 'true' or 'false'",
40
+ :safe => "must be 'true' or 'false'",
41
+ :w => "must be an integer specifying number of nodes to replica to",
42
+ :wtimeout => "must be an integer specifying milliseconds",
43
+ :fsync => "must be 'true' or 'false'"
44
+ }
45
+
46
+ OPT_CONV = {:connect => lambda {|arg| arg},
47
+ :replicaset => lambda {|arg| arg},
48
+ :slaveok => lambda {|arg| arg == 'true' ? true : false},
49
+ :safe => lambda {|arg| arg == 'true' ? true : false},
50
+ :w => lambda {|arg| arg.to_i},
51
+ :wtimeout => lambda {|arg| arg.to_i},
52
+ :fsync => lambda {|arg| arg == 'true' ? true : false}
53
+ }
54
+
55
+ attr_reader :nodes, :auths, :connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync
56
+
57
+ # Parse a MongoDB URI. This method is used by Connection.from_uri.
58
+ # Returns an array of nodes and an array of db authorizations, if applicable.
59
+ #
60
+ # Note: passwords can contain any character except for a ','.
61
+ #
62
+ # @core connections
63
+ def initialize(string)
64
+ if string =~ /^mongodb:\/\//
65
+ string = string[10..-1]
66
+ else
67
+ raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
68
+ end
69
+
70
+ hosts, opts = string.split('?')
71
+ parse_hosts(hosts)
72
+ parse_options(opts)
73
+ configure_connect
74
+ end
75
+
76
+ def connection_options
77
+ opts = {}
78
+
79
+ if (@w || @wtimeout || @fsync) && !@safe
80
+ raise MongoArgumentError, "Safe must be true if w, wtimeout, or fsync is specified"
81
+ end
82
+
83
+ if @safe
84
+ if @w || @wtimeout || @fsync
85
+ safe_opts = {}
86
+ safe_opts[:w] = @w if @w
87
+ safe_opts[:wtimeout] = @wtimeout if @wtimeout
88
+ safe_opts[:fsync] = @fsync if @fsync
89
+ else
90
+ safe_opts = true
91
+ end
92
+
93
+ opts[:safe] = safe_opts
94
+ end
95
+
96
+ if @slaveok
97
+ if @connect == 'direct'
98
+ opts[:slave_ok] = true
99
+ else
100
+ opts[:read_secondary] = true
101
+ end
102
+ end
103
+
104
+ opts[:rs_name] = @replicaset if @replicaset
105
+
106
+ opts
107
+ end
108
+
109
+ private
110
+
111
+ def parse_hosts(hosts)
112
+ @nodes = []
113
+ @auths = []
114
+ specs = hosts.split(',')
115
+ specs.each do |spec|
116
+ matches = MONGODB_URI_MATCHER.match(spec)
117
+ if !matches
118
+ raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
119
+ end
120
+
121
+ uname = matches[2]
122
+ pwd = matches[3]
123
+ host = matches[4]
124
+ port = matches[6] || DEFAULT_PORT
125
+ if !(port.to_s =~ /^\d+$/)
126
+ raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits."
127
+ end
128
+ port = port.to_i
129
+ db = matches[8]
130
+
131
+ if uname && pwd && db
132
+ auths << {'db_name' => db, 'username' => uname, 'password' => pwd}
133
+ elsif uname || pwd || db
134
+ raise MongoArgumentError, "MongoDB URI must include all three of username, password, " +
135
+ "and db if any one of these is specified."
136
+ end
137
+
138
+ @nodes << [host, port]
139
+ end
140
+ end
141
+
142
+ # This method uses the lambdas defined in OPT_VALID and OPT_CONV to validate
143
+ # and convert the given options.
144
+ def parse_options(opts)
145
+ return unless opts
146
+ separator = opts.include?('&') ? '&' : ';'
147
+ opts.split(separator).each do |attr|
148
+ key, value = attr.split('=')
149
+ key = key.to_sym
150
+ value = value.strip.downcase
151
+ if !OPT_ATTRS.include?(key)
152
+ raise MongoArgumentError, "Invalid Mongo URI option #{key}"
153
+ end
154
+
155
+ if OPT_VALID[key].call(value)
156
+ instance_variable_set("@#{key}", OPT_CONV[key].call(value))
157
+ else
158
+ raise MongoArgumentError, "Invalid value for #{key}: #{OPT_ERR[key]}"
159
+ end
160
+ end
161
+ end
162
+
163
+ def configure_connect
164
+ if @nodes.length > 1 && !@connect
165
+ @connect = 'replicaset'
166
+ end
167
+
168
+ if !@connect
169
+ if @nodes.length > 1
170
+ @connect = 'replicaset'
171
+ else
172
+ @connect = 'direct'
173
+ end
174
+ end
175
+
176
+ if @connect == 'direct' && @replicaset
177
+ raise MongoArgumentError, "If specifying a replica set name, please also specify that connect=replicaset"
178
+ end
179
+ end
180
+ end
181
+ end