mongo-lyon 1.2.4

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