jonbell-mongo 1.3.1.2

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 (88) hide show
  1. data/LICENSE.txt +190 -0
  2. data/README.md +333 -0
  3. data/Rakefile +215 -0
  4. data/bin/mongo_console +21 -0
  5. data/docs/CREDITS.md +123 -0
  6. data/docs/FAQ.md +116 -0
  7. data/docs/GridFS.md +158 -0
  8. data/docs/HISTORY.md +263 -0
  9. data/docs/RELEASES.md +33 -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 +97 -0
  14. data/lib/mongo/collection.rb +895 -0
  15. data/lib/mongo/connection.rb +926 -0
  16. data/lib/mongo/cursor.rb +474 -0
  17. data/lib/mongo/db.rb +617 -0
  18. data/lib/mongo/exceptions.rb +71 -0
  19. data/lib/mongo/gridfs/grid.rb +107 -0
  20. data/lib/mongo/gridfs/grid_ext.rb +57 -0
  21. data/lib/mongo/gridfs/grid_file_system.rb +146 -0
  22. data/lib/mongo/gridfs/grid_io.rb +485 -0
  23. data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
  24. data/lib/mongo/repl_set_connection.rb +356 -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 +177 -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 +185 -0
  31. data/mongo.gemspec +34 -0
  32. data/test/auxillary/1.4_features.rb +166 -0
  33. data/test/auxillary/authentication_test.rb +68 -0
  34. data/test/auxillary/autoreconnect_test.rb +41 -0
  35. data/test/auxillary/fork_test.rb +30 -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 +654 -0
  41. data/test/bson/byte_buffer_test.rb +208 -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 +210 -0
  46. data/test/bson/timestamp_test.rb +24 -0
  47. data/test/collection_test.rb +910 -0
  48. data/test/connection_test.rb +324 -0
  49. data/test/conversions_test.rb +119 -0
  50. data/test/cursor_fail_test.rb +75 -0
  51. data/test/cursor_message_test.rb +43 -0
  52. data/test/cursor_test.rb +483 -0
  53. data/test/db_api_test.rb +738 -0
  54. data/test/db_connection_test.rb +15 -0
  55. data/test/db_test.rb +315 -0
  56. data/test/grid_file_system_test.rb +259 -0
  57. data/test/grid_io_test.rb +209 -0
  58. data/test/grid_test.rb +258 -0
  59. data/test/load/thin/load.rb +24 -0
  60. data/test/load/unicorn/load.rb +23 -0
  61. data/test/replica_sets/connect_test.rb +112 -0
  62. data/test/replica_sets/connection_string_test.rb +32 -0
  63. data/test/replica_sets/count_test.rb +35 -0
  64. data/test/replica_sets/insert_test.rb +53 -0
  65. data/test/replica_sets/pooled_insert_test.rb +55 -0
  66. data/test/replica_sets/query_secondaries.rb +108 -0
  67. data/test/replica_sets/query_test.rb +51 -0
  68. data/test/replica_sets/replication_ack_test.rb +66 -0
  69. data/test/replica_sets/rs_test_helper.rb +27 -0
  70. data/test/safe_test.rb +68 -0
  71. data/test/support/hash_with_indifferent_access.rb +186 -0
  72. data/test/support/keys.rb +45 -0
  73. data/test/support_test.rb +18 -0
  74. data/test/test_helper.rb +102 -0
  75. data/test/threading/threading_with_large_pool_test.rb +90 -0
  76. data/test/threading_test.rb +87 -0
  77. data/test/tools/auth_repl_set_manager.rb +14 -0
  78. data/test/tools/repl_set_manager.rb +266 -0
  79. data/test/unit/collection_test.rb +130 -0
  80. data/test/unit/connection_test.rb +85 -0
  81. data/test/unit/cursor_test.rb +109 -0
  82. data/test/unit/db_test.rb +94 -0
  83. data/test/unit/grid_test.rb +49 -0
  84. data/test/unit/pool_test.rb +9 -0
  85. data/test/unit/repl_set_connection_test.rb +59 -0
  86. data/test/unit/safe_test.rb +125 -0
  87. data/test/uri_test.rb +91 -0
  88. metadata +224 -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,177 @@
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
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
+ # Pool size and timeout.
31
+ @size = opts[:size] || 1
32
+ @timeout = opts[:timeout] || 5.0
33
+
34
+ # Mutex for synchronizing pool access
35
+ @connection_mutex = Mutex.new
36
+
37
+ # Condition variable for signal and wait
38
+ @queue = ConditionVariable.new
39
+
40
+ # Operations to perform on a socket
41
+ @socket_ops = Hash.new { |h, k| h[k] = [] }
42
+
43
+ @sockets = []
44
+ @pids = {}
45
+ @checked_out = []
46
+ end
47
+
48
+ def close
49
+ @sockets.each do |sock|
50
+ begin
51
+ sock.close
52
+ rescue IOError => ex
53
+ warn "IOError when attempting to close socket connected to #{@host}:#{@port}: #{ex.inspect}"
54
+ end
55
+ end
56
+ @host = @port = nil
57
+ @sockets.clear
58
+ @pids.clear
59
+ @checked_out.clear
60
+ end
61
+
62
+ # Return a socket to the pool.
63
+ def checkin(socket)
64
+ @connection_mutex.synchronize do
65
+ @checked_out.delete(socket)
66
+ @queue.signal
67
+ end
68
+ true
69
+ end
70
+
71
+ # Adds a new socket to the pool and checks it out.
72
+ #
73
+ # This method is called exclusively from #checkout;
74
+ # therefore, it runs within a mutex.
75
+ def checkout_new_socket
76
+ begin
77
+ socket = TCPSocket.new(@host, @port)
78
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
79
+ rescue => ex
80
+ raise ConnectionFailure, "Failed to connect to host #{@host} and port #{@port}: #{ex}"
81
+ end
82
+
83
+ # If any saved authentications exist, we want to apply those
84
+ # when creating new sockets.
85
+ @connection.apply_saved_authentication(:socket => socket)
86
+
87
+ @sockets << socket
88
+ @pids[socket] = Process.pid
89
+ @checked_out << socket
90
+ socket
91
+ end
92
+
93
+ # If a user calls DB#authenticate, and several sockets exist,
94
+ # then we need a way to apply the authentication on each socket.
95
+ # So we store the apply_authentication method, and this will be
96
+ # applied right before the next use of each socket.
97
+ def authenticate_existing
98
+ @connection_mutex.synchronize do
99
+ @sockets.each do |socket|
100
+ @socket_ops[socket] << Proc.new do
101
+ @connection.apply_saved_authentication(:socket => socket)
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # Store the logout op for each existing socket to be applied before
108
+ # the next use of each socket.
109
+ def logout_existing(db)
110
+ @connection_mutex.synchronize do
111
+ @sockets.each do |socket|
112
+ @socket_ops[socket] << Proc.new do
113
+ @connection.db(db).issue_logout(:socket => socket)
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ # Checks out the first available socket from the pool.
120
+ #
121
+ # If the pid has changed, remove the socket and check out
122
+ # new one.
123
+ #
124
+ # This method is called exclusively from #checkout;
125
+ # therefore, it runs within a mutex.
126
+ def checkout_existing_socket
127
+ socket = (@sockets - @checked_out).first
128
+ if @pids[socket] != Process.pid
129
+ @pids[socket] = nil
130
+ @sockets.delete(socket)
131
+ socket.close
132
+ checkout_new_socket
133
+ else
134
+ @checked_out << socket
135
+ socket
136
+ end
137
+ end
138
+
139
+ # Check out an existing socket or create a new socket if the maximum
140
+ # pool size has not been exceeded. Otherwise, wait for the next
141
+ # available socket.
142
+ def checkout
143
+ @connection.connect if !@connection.connected?
144
+ start_time = Time.now
145
+ loop do
146
+ if (Time.now - start_time) > @timeout
147
+ raise ConnectionTimeoutError, "could not obtain connection within " +
148
+ "#{@timeout} seconds. The max pool size is currently #{@size}; " +
149
+ "consider increasing the pool size or timeout."
150
+ end
151
+
152
+ @connection_mutex.synchronize do
153
+ socket = if @checked_out.size < @sockets.size
154
+ checkout_existing_socket
155
+ elsif @sockets.size < @size
156
+ checkout_new_socket
157
+ end
158
+
159
+ if socket
160
+
161
+ # This calls all procs, in order, scoped to existing sockets.
162
+ # At the moment, we use this to lazily authenticate and
163
+ # logout existing socket connections.
164
+ @socket_ops[socket].reject! do |op|
165
+ op.call
166
+ end
167
+
168
+ return socket
169
+ else
170
+ # Otherwise, wait
171
+ @queue.wait(@connection_mutex)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ 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,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
+
19
+ module Mongo
20
+ class URIParser
21
+
22
+ DEFAULT_PORT = 27017
23
+ MONGODB_URI_MATCHER = /(([-.\w]+):([^@]+)@)?([-.\w]+)(:([\w]+))?(\/([-\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
+ # initialize instance variables for available options
146
+ OPT_VALID.keys.each { |k| instance_variable_set("@#{k}", nil) }
147
+
148
+ return unless opts
149
+
150
+ separator = opts.include?('&') ? '&' : ';'
151
+ opts.split(separator).each do |attr|
152
+ key, value = attr.split('=')
153
+ key = key.to_sym
154
+ value = value.strip.downcase
155
+ if !OPT_ATTRS.include?(key)
156
+ raise MongoArgumentError, "Invalid Mongo URI option #{key}"
157
+ end
158
+
159
+ if OPT_VALID[key].call(value)
160
+ instance_variable_set("@#{key}", OPT_CONV[key].call(value))
161
+ else
162
+ raise MongoArgumentError, "Invalid value for #{key}: #{OPT_ERR[key]}"
163
+ end
164
+ end
165
+ end
166
+
167
+ def configure_connect
168
+ if @nodes.length > 1 && !@connect
169
+ @connect = 'replicaset'
170
+ end
171
+
172
+ if !@connect
173
+ if @nodes.length > 1
174
+ @connect = 'replicaset'
175
+ else
176
+ @connect = 'direct'
177
+ end
178
+ end
179
+
180
+ if @connect == 'direct' && @replicaset
181
+ raise MongoArgumentError, "If specifying a replica set name, please also specify that connect=replicaset"
182
+ end
183
+ end
184
+ end
185
+ end