mongo 1.6.2 → 1.6.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.
- data/README.md +44 -22
- data/Rakefile +17 -4
- data/docs/GridFS.md +2 -2
- data/docs/HISTORY.md +15 -1
- data/docs/RELEASES.md +4 -4
- data/docs/TUTORIAL.md +12 -0
- data/docs/WRITE_CONCERN.md +1 -1
- data/lib/mongo/collection.rb +1 -1
- data/lib/mongo/connection.rb +35 -47
- data/lib/mongo/cursor.rb +10 -9
- data/lib/mongo/db.rb +1 -1
- data/lib/mongo/gridfs/grid_ext.rb +4 -4
- data/lib/mongo/gridfs/grid_file_system.rb +3 -3
- data/lib/mongo/gridfs/grid_io.rb +1 -1
- data/lib/mongo/networking.rb +5 -0
- data/lib/mongo/repl_set_connection.rb +47 -21
- data/lib/mongo/util/conversions.rb +23 -0
- data/lib/mongo/util/logging.rb +13 -18
- data/lib/mongo/util/node.rb +1 -5
- data/lib/mongo/util/pool.rb +0 -1
- data/lib/mongo/util/ssl_socket.rb +3 -1
- data/lib/mongo/util/support.rb +1 -0
- data/lib/mongo/util/tcp_socket.rb +15 -32
- data/lib/mongo/util/uri_parser.rb +100 -35
- data/lib/mongo/version.rb +1 -1
- data/test/auxillary/1.4_features.rb +1 -1
- data/test/auxillary/authentication_test.rb +1 -1
- data/test/auxillary/autoreconnect_test.rb +1 -1
- data/test/auxillary/fork_test.rb +1 -1
- data/test/auxillary/repl_set_auth_test.rb +1 -1
- data/test/auxillary/slave_connection_test.rb +1 -1
- data/test/auxillary/threaded_authentication_test.rb +1 -1
- data/test/bson/binary_test.rb +1 -1
- data/test/bson/bson_test.rb +8 -1
- data/test/bson/byte_buffer_test.rb +1 -1
- data/test/bson/hash_with_indifferent_access_test.rb +1 -1
- data/test/bson/json_test.rb +1 -1
- data/test/bson/object_id_test.rb +11 -1
- data/test/bson/ordered_hash_test.rb +1 -1
- data/test/bson/timestamp_test.rb +1 -1
- data/test/collection_test.rb +1 -1
- data/test/connection_test.rb +25 -1
- data/test/conversions_test.rb +1 -1
- data/test/cursor_fail_test.rb +1 -1
- data/test/cursor_message_test.rb +1 -1
- data/test/cursor_test.rb +1 -1
- data/test/db_api_test.rb +57 -3
- data/test/db_connection_test.rb +1 -1
- data/test/db_test.rb +1 -1
- data/test/grid_file_system_test.rb +1 -1
- data/test/grid_io_test.rb +30 -1
- data/test/grid_test.rb +1 -1
- data/test/pool_test.rb +1 -1
- data/test/replica_sets/basic_test.rb +10 -0
- data/test/replica_sets/connect_test.rb +45 -0
- data/test/replica_sets/read_preference_test.rb +2 -1
- data/test/replica_sets/refresh_with_threads_test.rb +2 -0
- data/test/replica_sets/rs_test_helper.rb +1 -1
- data/test/safe_test.rb +1 -1
- data/test/support_test.rb +1 -1
- data/test/threading/threading_with_large_pool_test.rb +1 -1
- data/test/threading_test.rb +1 -1
- data/test/timeout_test.rb +1 -1
- data/test/tools/repl_set_manager.rb +1 -0
- data/test/unit/collection_test.rb +1 -1
- data/test/unit/connection_test.rb +89 -1
- data/test/unit/cursor_test.rb +1 -1
- data/test/unit/db_test.rb +1 -1
- data/test/unit/grid_test.rb +1 -1
- data/test/unit/node_test.rb +1 -1
- data/test/unit/pool_manager_test.rb +1 -1
- data/test/unit/pool_test.rb +1 -1
- data/test/unit/read_test.rb +1 -1
- data/test/unit/safe_test.rb +1 -1
- data/test/uri_test.rb +25 -5
- metadata +5 -5
@@ -33,19 +33,19 @@ module Mongo
|
|
33
33
|
# @example
|
34
34
|
#
|
35
35
|
# # Check for the existence of a given filename
|
36
|
-
# @grid = GridFileSystem.new(@db)
|
36
|
+
# @grid = Mongo::GridFileSystem.new(@db)
|
37
37
|
# @grid.exist?(:filename => 'foo.txt')
|
38
38
|
#
|
39
39
|
# # Check for existence filename and content type
|
40
|
-
# @grid = GridFileSystem.new(@db)
|
40
|
+
# @grid = Mongo::GridFileSystem.new(@db)
|
41
41
|
# @grid.exist?(:filename => 'foo.txt', :content_type => 'image/jpg')
|
42
42
|
#
|
43
43
|
# # Check for existence by _id
|
44
|
-
# @grid = Grid.new(@db)
|
44
|
+
# @grid = Mongo::Grid.new(@db)
|
45
45
|
# @grid.exist?(:_id => BSON::ObjectId.from_string('4bddcd24beffd95a7db9b8c8'))
|
46
46
|
#
|
47
47
|
# # Check for existence by an arbitrary attribute.
|
48
|
-
# @grid = Grid.new(@db)
|
48
|
+
# @grid = Mongo::Grid.new(@db)
|
49
49
|
# @grid.exist?(:tags => {'$in' => ['nature', 'zen', 'photography']})
|
50
50
|
#
|
51
51
|
# @return [nil, Hash] either nil for the file's metadata as a hash.
|
@@ -78,20 +78,20 @@ module Mongo
|
|
78
78
|
# @example
|
79
79
|
#
|
80
80
|
# # Store the text "Hello, world!" in the grid file system.
|
81
|
-
# @grid = GridFileSystem.new(@db)
|
81
|
+
# @grid = Mongo::GridFileSystem.new(@db)
|
82
82
|
# @grid.open('filename', 'w') do |f|
|
83
83
|
# f.write "Hello, world!"
|
84
84
|
# end
|
85
85
|
#
|
86
86
|
# # Output "Hello, world!"
|
87
|
-
# @grid = GridFileSystem.new(@db)
|
87
|
+
# @grid = Mongo::GridFileSystem.new(@db)
|
88
88
|
# @grid.open('filename', 'r') do |f|
|
89
89
|
# puts f.read
|
90
90
|
# end
|
91
91
|
#
|
92
92
|
# # Write a file on disk to the GridFileSystem
|
93
93
|
# @file = File.open('image.jpg')
|
94
|
-
# @grid = GridFileSystem.new(@db)
|
94
|
+
# @grid = Mongo::GridFileSystem.new(@db)
|
95
95
|
# @grid.open('image.jpg, 'w') do |f|
|
96
96
|
# f.write @file
|
97
97
|
# end
|
data/lib/mongo/gridfs/grid_io.rb
CHANGED
data/lib/mongo/networking.rb
CHANGED
@@ -140,6 +140,11 @@ module Mongo
|
|
140
140
|
rescue SystemStackError, NoMemoryError, SystemCallError => ex
|
141
141
|
close
|
142
142
|
raise ex
|
143
|
+
rescue Exception => ex
|
144
|
+
if defined?(IRB)
|
145
|
+
close if ex.class == IRB::Abort
|
146
|
+
end
|
147
|
+
raise ex
|
143
148
|
ensure
|
144
149
|
if should_checkin
|
145
150
|
if command || read == :primary
|
@@ -25,10 +25,12 @@ module Mongo
|
|
25
25
|
:read_secondary, :rs_name, :name]
|
26
26
|
|
27
27
|
attr_reader :replica_set_name, :seeds, :refresh_interval, :refresh_mode,
|
28
|
-
:refresh_version
|
28
|
+
:refresh_version, :manager
|
29
29
|
|
30
30
|
# Create a connection to a MongoDB replica set.
|
31
31
|
#
|
32
|
+
# If no args are provided, it will check <code>ENV["MONGODB_URI"]</code>.
|
33
|
+
#
|
32
34
|
# Once connected to a replica set, you can find out which nodes are primary, secondary, and
|
33
35
|
# arbiters with the corresponding accessors: Connection#primary, Connection#secondaries, and
|
34
36
|
# Connection#arbiters. This is useful if your application needs to connect manually to nodes other
|
@@ -68,16 +70,18 @@ module Mongo
|
|
68
70
|
# The purpose of seed nodes is to permit the driver to find at least one replica set member even if a member is down.
|
69
71
|
#
|
70
72
|
# @example Connect to a replica set and provide two seed nodes.
|
71
|
-
# ReplSetConnection.new(['localhost:30000', 'localhost:30001'])
|
73
|
+
# Mongo::ReplSetConnection.new(['localhost:30000', 'localhost:30001'])
|
72
74
|
#
|
73
75
|
# @example Connect to a replica set providing two seed nodes and ensuring a connection to the replica set named 'prod':
|
74
|
-
# ReplSetConnection.new(['localhost:30000', 'localhost:30001'], :name => 'prod')
|
76
|
+
# Mongo::ReplSetConnection.new(['localhost:30000', 'localhost:30001'], :name => 'prod')
|
75
77
|
#
|
76
78
|
# @example Connect to a replica set providing two seed nodes and allowing reads from a secondary node:
|
77
|
-
# ReplSetConnection.new(['localhost:30000', 'localhost:30001'], :read => :secondary)
|
79
|
+
# Mongo::ReplSetConnection.new(['localhost:30000', 'localhost:30001'], :read => :secondary)
|
78
80
|
#
|
79
81
|
# @see http://api.mongodb.org/ruby/current/file.REPLICA_SETS.html Replica sets in Ruby
|
80
82
|
#
|
83
|
+
# @raise [MongoArgumentError] If called with no arguments and <code>ENV["MONGODB_URI"]</code> implies a direct connection.
|
84
|
+
#
|
81
85
|
# @raise [ReplicaSetConnectionError] This is raised if a replica set name is specified and the
|
82
86
|
# driver fails to connect to a replica set with that name.
|
83
87
|
def initialize(*args)
|
@@ -87,21 +91,30 @@ module Mongo
|
|
87
91
|
opts = {}
|
88
92
|
end
|
89
93
|
|
90
|
-
|
94
|
+
nodes = args
|
95
|
+
|
96
|
+
if nodes.empty? and ENV.has_key?('MONGODB_URI')
|
97
|
+
parser = URIParser.new ENV['MONGODB_URI'], opts
|
98
|
+
if parser.direct?
|
99
|
+
raise MongoArgumentError, "Mongo::ReplSetConnection.new called with no arguments, but ENV['MONGODB_URI'] implies a direct connection."
|
100
|
+
end
|
101
|
+
opts = parser.connection_options
|
102
|
+
nodes = parser.nodes
|
103
|
+
end
|
104
|
+
|
105
|
+
unless nodes.length > 0
|
91
106
|
raise MongoArgumentError, "A ReplSetConnection requires at least one seed node."
|
92
107
|
end
|
93
108
|
|
94
109
|
# This is temporary until support for the old format is dropped
|
95
|
-
|
96
|
-
if args.first.last.is_a?(Integer)
|
110
|
+
if nodes.first.last.is_a?(Integer)
|
97
111
|
warn "Initiating a ReplSetConnection with seeds passed as individual [host, port] array arguments is deprecated."
|
98
112
|
warn "Please specify hosts as an array of 'host:port' strings; the old format will be removed in v2.0"
|
99
|
-
@seeds =
|
113
|
+
@seeds = nodes
|
100
114
|
else
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
seeds << seed
|
115
|
+
@seeds = nodes.first.map do |host_port|
|
116
|
+
host, port = host_port.split(":")
|
117
|
+
[ host, port.to_i ]
|
105
118
|
end
|
106
119
|
end
|
107
120
|
|
@@ -149,8 +162,9 @@ module Mongo
|
|
149
162
|
|
150
163
|
discovered_seeds = @manager ? @manager.seeds : []
|
151
164
|
@manager = PoolManager.new(self, discovered_seeds)
|
152
|
-
|
153
|
-
Thread.current[:
|
165
|
+
|
166
|
+
Thread.current[:managers] ||= Hash.new
|
167
|
+
Thread.current[:managers][self] = @manager
|
154
168
|
|
155
169
|
@manager.connect
|
156
170
|
@refresh_version += 1
|
@@ -203,7 +217,7 @@ module Mongo
|
|
203
217
|
new_manager = PoolManager.new(self, discovered_seeds | @seeds)
|
204
218
|
new_manager.connect
|
205
219
|
|
206
|
-
Thread.current[:
|
220
|
+
Thread.current[:managers][self] = new_manager
|
207
221
|
|
208
222
|
# TODO: make sure that connect has succeeded
|
209
223
|
@old_managers << @manager
|
@@ -263,6 +277,12 @@ module Mongo
|
|
263
277
|
else
|
264
278
|
@manager.close if @manager
|
265
279
|
end
|
280
|
+
|
281
|
+
# Clear the reference to this object.
|
282
|
+
if Thread.current[:managers]
|
283
|
+
Thread.current[:managers].delete(self)
|
284
|
+
end
|
285
|
+
|
266
286
|
@connected = false
|
267
287
|
end
|
268
288
|
|
@@ -394,11 +414,17 @@ module Mongo
|
|
394
414
|
end
|
395
415
|
end
|
396
416
|
|
397
|
-
def
|
398
|
-
|
399
|
-
|
417
|
+
def ensure_manager
|
418
|
+
Thread.current[:managers] ||= Hash.new
|
419
|
+
|
420
|
+
if Thread.current[:managers][self] != @manager
|
421
|
+
Thread.current[:managers][self] = @manager
|
400
422
|
end
|
401
|
-
|
423
|
+
end
|
424
|
+
|
425
|
+
def get_socket_from_pool(pool_type)
|
426
|
+
ensure_manager
|
427
|
+
|
402
428
|
pool = case pool_type
|
403
429
|
when :primary
|
404
430
|
primary_pool
|
@@ -417,9 +443,9 @@ module Mongo
|
|
417
443
|
return nil
|
418
444
|
end
|
419
445
|
end
|
420
|
-
|
446
|
+
|
421
447
|
def local_manager
|
422
|
-
Thread.current[:
|
448
|
+
Thread.current[:managers][self] if Thread.current[:managers]
|
423
449
|
end
|
424
450
|
|
425
451
|
def arbiters
|
@@ -24,6 +24,29 @@ module Mongo #:nodoc:
|
|
24
24
|
ASCENDING_CONVERSION = ["ascending", "asc", "1"]
|
25
25
|
DESCENDING_CONVERSION = ["descending", "desc", "-1"]
|
26
26
|
|
27
|
+
# Allows sort parameters to be defined as a Hash.
|
28
|
+
# Does not allow usage of un-ordered hashes, therefore
|
29
|
+
# Ruby 1.8.x users must use BSON::OrderedHash.
|
30
|
+
#
|
31
|
+
# Example:
|
32
|
+
#
|
33
|
+
# <tt>hash_as_sort_parameters({:field1 => :asc, "field2" => :desc})</tt> =>
|
34
|
+
# <tt>{ "field1" => 1, "field2" => -1}</tt>
|
35
|
+
def hash_as_sort_parameters(value)
|
36
|
+
if RUBY_VERSION < '1.9' && !value.is_a?(BSON::OrderedHash)
|
37
|
+
raise InvalidSortValueError.new(
|
38
|
+
"Hashes used to supply sort order must maintain ordering." +
|
39
|
+
"Use BSON::OrderedHash."
|
40
|
+
)
|
41
|
+
else
|
42
|
+
order_by = value.inject({}) do |memo, (key, direction)|
|
43
|
+
memo[key.to_s] = sort_value(direction.to_s.downcase)
|
44
|
+
memo
|
45
|
+
end
|
46
|
+
end
|
47
|
+
order_by
|
48
|
+
end
|
49
|
+
|
27
50
|
# Converts the supplied +Array+ to a +Hash+ to pass to mongo as
|
28
51
|
# sorting parameters. The returned +Hash+ will vary depending
|
29
52
|
# on whether the passed +Array+ is one or two dimensional.
|
data/lib/mongo/util/logging.rb
CHANGED
@@ -1,14 +1,10 @@
|
|
1
1
|
module Mongo
|
2
2
|
module Logging
|
3
3
|
|
4
|
-
DEBUG_LEVEL = defined?(Logger) ? Logger::DEBUG : 0
|
5
|
-
|
6
4
|
def write_logging_startup_message
|
7
|
-
|
8
|
-
log(:debug, "Logging level is currently :debug which could negatively impact " +
|
5
|
+
log(:debug, "Logging level is currently :debug which could negatively impact " +
|
9
6
|
"client-side performance. You should set your logging level no lower than " +
|
10
7
|
":info in production.")
|
11
|
-
end
|
12
8
|
end
|
13
9
|
|
14
10
|
# Log a message with the given level.
|
@@ -31,27 +27,26 @@ module Mongo
|
|
31
27
|
end
|
32
28
|
|
33
29
|
# Execute the block and log the operation described by name and payload.
|
34
|
-
def instrument(name, payload = {}
|
30
|
+
def instrument(name, payload = {})
|
35
31
|
start_time = Time.now
|
36
32
|
res = yield
|
37
|
-
|
38
|
-
log_operation(name, payload, start_time)
|
39
|
-
end
|
33
|
+
log_operation(name, payload, start_time)
|
40
34
|
res
|
41
35
|
end
|
42
36
|
|
43
37
|
protected
|
44
38
|
|
45
39
|
def log_operation(name, payload, start_time)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
40
|
+
@logger && @logger.debug do
|
41
|
+
msg = "MONGODB "
|
42
|
+
msg << "(#{((Time.now - start_time) * 1000).to_i}ms) "
|
43
|
+
msg << "#{payload[:database]}['#{payload[:collection]}'].#{name}("
|
44
|
+
msg << payload.values_at(:selector, :document, :documents, :fields ).compact.map(&:inspect).join(', ') + ")"
|
45
|
+
msg << ".skip(#{payload[:skip]})" if payload[:skip]
|
46
|
+
msg << ".limit(#{payload[:limit]})" if payload[:limit]
|
47
|
+
msg << ".sort(#{payload[:order]})" if payload[:order]
|
48
|
+
msg
|
49
|
+
end
|
55
50
|
end
|
56
51
|
|
57
52
|
end
|
data/lib/mongo/util/node.rb
CHANGED
@@ -40,11 +40,7 @@ module Mongo
|
|
40
40
|
@connection.op_timeout, @connection.connect_timeout
|
41
41
|
)
|
42
42
|
|
43
|
-
if socket.nil?
|
44
|
-
return nil
|
45
|
-
else
|
46
|
-
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
47
|
-
end
|
43
|
+
return nil if socket.nil?
|
48
44
|
rescue OperationTimeout, ConnectionFailure, OperationFailure, SocketError, SystemCallError, IOError => ex
|
49
45
|
@connection.log(:debug, "Failed connection to #{host_string} with #{ex.class}, #{ex.message}.")
|
50
46
|
socket.close if socket
|
data/lib/mongo/util/pool.rb
CHANGED
@@ -157,7 +157,6 @@ module Mongo
|
|
157
157
|
def checkout_new_socket
|
158
158
|
begin
|
159
159
|
socket = @connection.socket_class.new(@host, @port, @connection.op_timeout)
|
160
|
-
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
161
160
|
socket.pool = self
|
162
161
|
rescue => ex
|
163
162
|
socket.close if socket
|
@@ -15,7 +15,9 @@ module Mongo
|
|
15
15
|
@op_timeout = op_timeout
|
16
16
|
@connect_timeout = connect_timeout
|
17
17
|
|
18
|
-
@socket = ::TCPSocket.new(host, port)
|
18
|
+
@socket = ::TCPSocket.new(host, port)
|
19
|
+
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
20
|
+
|
19
21
|
@ssl = OpenSSL::SSL::SSLSocket.new(@socket)
|
20
22
|
@ssl.sync_close = true
|
21
23
|
|
data/lib/mongo/util/support.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require 'timeout'
|
2
3
|
|
3
4
|
module Mongo
|
4
5
|
# Wrapper class for Socket
|
@@ -16,41 +17,21 @@ module Mongo
|
|
16
17
|
# TODO: Prefer ipv6 if server is ipv6 enabled
|
17
18
|
@host = Socket.getaddrinfo(host, nil, Socket::AF_INET).first[3]
|
18
19
|
@port = port
|
20
|
+
|
19
21
|
@socket_address = Socket.pack_sockaddr_in(@port, @host)
|
20
22
|
@socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
23
|
+
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
21
24
|
|
22
25
|
connect
|
23
26
|
end
|
24
27
|
|
25
28
|
def connect
|
26
|
-
|
27
|
-
|
28
|
-
require 'timeout'
|
29
|
-
if @connect_timeout
|
30
|
-
Timeout::timeout(@connect_timeout, OperationTimeout) do
|
31
|
-
@socket.connect(@socket_address)
|
32
|
-
end
|
33
|
-
else
|
29
|
+
if @connect_timeout
|
30
|
+
Timeout::timeout(@connect_timeout, OperationTimeout) do
|
34
31
|
@socket.connect(@socket_address)
|
35
32
|
end
|
36
33
|
else
|
37
|
-
|
38
|
-
begin
|
39
|
-
@socket.connect_nonblock(@socket_address)
|
40
|
-
rescue Errno::EINPROGRESS
|
41
|
-
# Block until there is a response or error
|
42
|
-
resp = IO.select([@socket], [@socket], [@socket], @connect_timeout)
|
43
|
-
if resp.nil?
|
44
|
-
raise ConnectionTimeoutError
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# If there was a failure this will raise an Error
|
49
|
-
begin
|
50
|
-
@socket.connect_nonblock(@socket_address)
|
51
|
-
rescue Errno::EISCONN
|
52
|
-
# Successfully connected
|
53
|
-
end
|
34
|
+
@socket.connect(@socket_address)
|
54
35
|
end
|
55
36
|
end
|
56
37
|
|
@@ -67,13 +48,15 @@ module Mongo
|
|
67
48
|
end
|
68
49
|
if ready
|
69
50
|
begin
|
70
|
-
@socket.
|
71
|
-
rescue
|
72
|
-
|
73
|
-
|
74
|
-
raise ConnectionFailure
|
75
|
-
rescue Errno::
|
76
|
-
raise
|
51
|
+
@socket.sysread(maxlen, buffer)
|
52
|
+
rescue SystemCallError => ex
|
53
|
+
# Needed because sometimes JRUBY doesn't throw Errno::ECONNRESET as it should
|
54
|
+
# http://jira.codehaus.org/browse/JRUBY-6180
|
55
|
+
raise ConnectionFailure, ex
|
56
|
+
rescue Errno::ENOTCONN, Errno::EBADF, Errno::ECONNRESET, Errno::EPIPE, Errno::ETIMEDOUT, EOFError => ex
|
57
|
+
raise ConnectionFailure, ex
|
58
|
+
rescue Errno::EINTR, Errno::EIO, IOError => ex
|
59
|
+
raise OperationFailure, ex
|
77
60
|
end
|
78
61
|
else
|
79
62
|
raise OperationTimeout
|
@@ -16,11 +16,11 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
# ++
|
18
18
|
|
19
|
+
require 'cgi'
|
20
|
+
|
19
21
|
module Mongo
|
20
22
|
class URIParser
|
21
23
|
|
22
|
-
DEFAULT_PORT = 27017
|
23
|
-
|
24
24
|
USER_REGEX = /([-.\w:]+)/
|
25
25
|
PASS_REGEX = /([^@,]+)/
|
26
26
|
AUTH_REGEX = /(#{USER_REGEX}:#{PASS_REGEX}@)?/
|
@@ -37,7 +37,7 @@ module Mongo
|
|
37
37
|
SPEC_ATTRS = [:nodes, :auths]
|
38
38
|
OPT_ATTRS = [:connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync, :journal, :connecttimeoutms, :sockettimeoutms, :wtimeoutms]
|
39
39
|
|
40
|
-
OPT_VALID = {:connect => lambda {|arg| ['direct', 'replicaset'].include?(arg)},
|
40
|
+
OPT_VALID = {:connect => lambda {|arg| ['direct', 'replicaset', 'true', 'false', true, false].include?(arg)},
|
41
41
|
:replicaset => lambda {|arg| arg.length > 0},
|
42
42
|
:slaveok => lambda {|arg| ['true', 'false'].include?(arg)},
|
43
43
|
:safe => lambda {|arg| ['true', 'false'].include?(arg)},
|
@@ -50,7 +50,7 @@ module Mongo
|
|
50
50
|
:wtimeoutms => lambda {|arg| arg =~ /^\d+$/ }
|
51
51
|
}
|
52
52
|
|
53
|
-
OPT_ERR = {:connect => "must be 'direct' or '
|
53
|
+
OPT_ERR = {:connect => "must be 'direct', 'replicaset', 'true', or 'false'",
|
54
54
|
:replicaset => "must be a string containing the name of the replica set to connect to",
|
55
55
|
:slaveok => "must be 'true' or 'false'",
|
56
56
|
:safe => "must be 'true' or 'false'",
|
@@ -63,7 +63,7 @@ module Mongo
|
|
63
63
|
:wtimeoutms => "must be an integer specifying milliseconds"
|
64
64
|
}
|
65
65
|
|
66
|
-
OPT_CONV = {:connect => lambda {|arg| arg},
|
66
|
+
OPT_CONV = {:connect => lambda {|arg| arg == 'false' ? false : arg}, # be sure to convert 'false' to FalseClass
|
67
67
|
:replicaset => lambda {|arg| arg},
|
68
68
|
:slaveok => lambda {|arg| arg == 'true' ? true : false},
|
69
69
|
:safe => lambda {|arg| arg == 'true' ? true : false},
|
@@ -81,22 +81,73 @@ module Mongo
|
|
81
81
|
# Parse a MongoDB URI. This method is used by Connection.from_uri.
|
82
82
|
# Returns an array of nodes and an array of db authorizations, if applicable.
|
83
83
|
#
|
84
|
-
#
|
84
|
+
# @note Passwords can contain any character except for ','
|
85
|
+
#
|
86
|
+
# @param [String] uri The MongoDB URI string.
|
87
|
+
# @param [Hash,nil] extra_opts Extra options. Will override anything already specified in the URI.
|
85
88
|
#
|
86
89
|
# @core connections
|
87
|
-
def initialize(
|
88
|
-
if
|
89
|
-
|
90
|
+
def initialize(uri, extra_opts={})
|
91
|
+
if uri.start_with?('mongodb://')
|
92
|
+
uri = uri[10..-1]
|
90
93
|
else
|
91
94
|
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
92
95
|
end
|
93
96
|
|
94
|
-
hosts, opts =
|
97
|
+
hosts, opts = uri.split('?')
|
95
98
|
parse_hosts(hosts)
|
96
|
-
parse_options(opts)
|
97
|
-
|
99
|
+
parse_options(opts, extra_opts)
|
100
|
+
validate_connect
|
101
|
+
end
|
102
|
+
|
103
|
+
# Create a Mongo::Connection or a Mongo::ReplSetConnection based on the URI.
|
104
|
+
#
|
105
|
+
# @note Don't confuse this with attribute getter method #connect.
|
106
|
+
#
|
107
|
+
# @return [Connection,ReplSetConnection]
|
108
|
+
def connection
|
109
|
+
if replicaset?
|
110
|
+
ReplSetConnection.new(*(nodes+[connection_options]))
|
111
|
+
else
|
112
|
+
Connection.new(host, port, connection_options)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Whether this represents a replica set.
|
117
|
+
# @return [true,false]
|
118
|
+
def replicaset?
|
119
|
+
replicaset.is_a?(String) || nodes.length > 1
|
120
|
+
end
|
121
|
+
|
122
|
+
# Whether to immediately connect to the MongoDB node[s]. Defaults to true.
|
123
|
+
# @return [true, false]
|
124
|
+
def connect?
|
125
|
+
connect != false
|
126
|
+
end
|
127
|
+
|
128
|
+
# Whether this represents a direct connection.
|
129
|
+
#
|
130
|
+
# @note Specifying :connect => 'direct' has no effect... other than to raise an exception if other variables suggest a replicaset.
|
131
|
+
#
|
132
|
+
# @return [true,false]
|
133
|
+
def direct?
|
134
|
+
!replicaset?
|
135
|
+
end
|
136
|
+
|
137
|
+
# For direct connections, the host of the (only) node.
|
138
|
+
# @return [String]
|
139
|
+
def host
|
140
|
+
nodes[0][0]
|
98
141
|
end
|
99
142
|
|
143
|
+
# For direct connections, the port of the (only) node.
|
144
|
+
# @return [Integer]
|
145
|
+
def port
|
146
|
+
nodes[0][1].to_i
|
147
|
+
end
|
148
|
+
|
149
|
+
# Options that can be passed to Mongo::Connection.new or Mongo::ReplSetConnection.new
|
150
|
+
# @return [Hash]
|
100
151
|
def connection_options
|
101
152
|
opts = {}
|
102
153
|
|
@@ -136,14 +187,22 @@ module Mongo
|
|
136
187
|
end
|
137
188
|
|
138
189
|
if @slaveok
|
139
|
-
if
|
190
|
+
if direct?
|
140
191
|
opts[:slave_ok] = true
|
141
192
|
else
|
142
193
|
opts[:read] = :secondary
|
143
194
|
end
|
144
195
|
end
|
145
196
|
|
146
|
-
|
197
|
+
if direct?
|
198
|
+
opts[:auths] = auths
|
199
|
+
end
|
200
|
+
|
201
|
+
if replicaset.is_a?(String)
|
202
|
+
opts[:name] = replicaset
|
203
|
+
end
|
204
|
+
|
205
|
+
opts[:connect] = connect?
|
147
206
|
|
148
207
|
opts
|
149
208
|
end
|
@@ -167,7 +226,7 @@ module Mongo
|
|
167
226
|
|
168
227
|
hosturis.each do |hosturi|
|
169
228
|
# If port is present, use it, otherwise use default port
|
170
|
-
host, port = hosturi.split(':') + [DEFAULT_PORT]
|
229
|
+
host, port = hosturi.split(':') + [Connection::DEFAULT_PORT]
|
171
230
|
|
172
231
|
if !(port.to_s =~ /^\d+$/)
|
173
232
|
raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits."
|
@@ -178,6 +237,10 @@ module Mongo
|
|
178
237
|
@nodes << [host, port]
|
179
238
|
end
|
180
239
|
|
240
|
+
if @nodes.empty?
|
241
|
+
raise MongoArgumentError, "No nodes specified. Please ensure that you've provided at least one node."
|
242
|
+
end
|
243
|
+
|
181
244
|
if uname && pwd && db
|
182
245
|
auths << {'db_name' => db, 'username' => uname, 'password' => pwd}
|
183
246
|
elsif uname || pwd
|
@@ -191,40 +254,42 @@ module Mongo
|
|
191
254
|
|
192
255
|
# This method uses the lambdas defined in OPT_VALID and OPT_CONV to validate
|
193
256
|
# and convert the given options.
|
194
|
-
def parse_options(
|
257
|
+
def parse_options(string_opts, extra_opts={})
|
195
258
|
# initialize instance variables for available options
|
196
259
|
OPT_VALID.keys.each { |k| instance_variable_set("@#{k}", nil) }
|
197
260
|
|
198
|
-
|
261
|
+
string_opts ||= ''
|
199
262
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
263
|
+
return if string_opts.empty? && extra_opts.empty?
|
264
|
+
|
265
|
+
if string_opts.include?(';') and string_opts.include?('&')
|
266
|
+
raise MongoArgumentError, "must not mix URL separators ; and &"
|
267
|
+
end
|
268
|
+
|
269
|
+
opts = CGI.parse(string_opts).inject({}) do |memo, (key, value)|
|
270
|
+
value = value.first
|
271
|
+
memo[key.downcase.to_sym] = value.strip.downcase
|
272
|
+
memo
|
273
|
+
end
|
274
|
+
|
275
|
+
opts.merge! extra_opts
|
276
|
+
|
277
|
+
opts.each do |key, value|
|
205
278
|
if !OPT_ATTRS.include?(key)
|
206
279
|
raise MongoArgumentError, "Invalid Mongo URI option #{key}"
|
207
280
|
end
|
208
|
-
|
209
281
|
if OPT_VALID[key].call(value)
|
210
282
|
instance_variable_set("@#{key}", OPT_CONV[key].call(value))
|
211
283
|
else
|
212
|
-
raise MongoArgumentError, "Invalid value for #{key}: #{OPT_ERR[key]}"
|
284
|
+
raise MongoArgumentError, "Invalid value #{value.inspect} for #{key}: #{OPT_ERR[key]}"
|
213
285
|
end
|
214
286
|
end
|
215
287
|
end
|
216
288
|
|
217
|
-
def
|
218
|
-
if
|
219
|
-
|
220
|
-
|
221
|
-
else
|
222
|
-
@connect = 'direct'
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
if @connect == 'direct' && @replicaset
|
227
|
-
raise MongoArgumentError, "If specifying a replica set name, please also specify that connect=replicaset"
|
289
|
+
def validate_connect
|
290
|
+
if replicaset? and @connect == 'direct'
|
291
|
+
# Make sure the user doesn't specify something contradictory
|
292
|
+
raise MongoArgumentError, "connect=direct conflicts with setting a replicaset name"
|
228
293
|
end
|
229
294
|
end
|
230
295
|
end
|