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