mongo 1.1.5 → 1.3.0
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 +15 -15
- data/Rakefile +38 -17
- data/docs/FAQ.md +4 -0
- data/docs/HISTORY.md +59 -0
- data/docs/RELEASES.md +33 -0
- data/docs/REPLICA_SETS.md +13 -16
- data/lib/mongo/collection.rb +157 -69
- data/lib/mongo/connection.rb +189 -65
- data/lib/mongo/cursor.rb +43 -29
- data/lib/mongo/db.rb +63 -43
- data/lib/mongo/exceptions.rb +4 -1
- data/lib/mongo/gridfs/grid.rb +1 -1
- data/lib/mongo/gridfs/grid_ext.rb +1 -1
- data/lib/mongo/gridfs/grid_file_system.rb +1 -1
- data/lib/mongo/gridfs/grid_io.rb +89 -8
- data/lib/mongo/gridfs/grid_io_fix.rb +1 -1
- data/lib/mongo/repl_set_connection.rb +72 -20
- data/lib/mongo/test.rb +20 -0
- data/lib/mongo/util/conversions.rb +1 -1
- data/lib/mongo/util/core_ext.rb +11 -1
- data/lib/mongo/util/pool.rb +67 -15
- data/lib/mongo/util/server_version.rb +1 -1
- data/lib/mongo/util/support.rb +1 -1
- data/lib/mongo/util/uri_parser.rb +127 -13
- data/lib/mongo.rb +38 -2
- data/test/async/collection_test.rb +224 -0
- data/test/async/connection_test.rb +24 -0
- data/test/async/cursor_test.rb +162 -0
- data/test/async/worker_pool_test.rb +99 -0
- data/test/auxillary/fork_test.rb +30 -0
- data/test/auxillary/repl_set_auth_test.rb +58 -0
- data/test/auxillary/threaded_authentication_test.rb +101 -0
- data/test/bson/bson_test.rb +140 -28
- data/test/bson/byte_buffer_test.rb +18 -0
- data/test/bson/object_id_test.rb +14 -1
- data/test/bson/ordered_hash_test.rb +7 -0
- data/test/bson/timestamp_test.rb +24 -0
- data/test/collection_test.rb +104 -15
- data/test/connection_test.rb +78 -2
- data/test/conversions_test.rb +10 -11
- data/test/cursor_fail_test.rb +1 -1
- data/test/cursor_message_test.rb +1 -1
- data/test/cursor_test.rb +33 -4
- data/test/db_api_test.rb +30 -52
- data/test/db_test.rb +3 -3
- data/test/grid_file_system_test.rb +0 -1
- data/test/grid_io_test.rb +72 -1
- data/test/grid_test.rb +16 -16
- data/test/load/resque/load.rb +21 -0
- data/test/load/resque/processor.rb +26 -0
- data/test/load/thin/load.rb +24 -0
- data/test/load/unicorn/load.rb +23 -0
- data/test/load/unicorn/unicorn.rb +29 -0
- data/test/replica_sets/connect_test.rb +11 -1
- data/test/replica_sets/connection_string_test.rb +32 -0
- data/test/replica_sets/query_secondaries.rb +16 -0
- data/test/replica_sets/query_test.rb +10 -0
- data/test/replica_sets/replication_ack_test.rb +2 -0
- data/test/replica_sets/rs_test_helper.rb +9 -11
- data/test/support/hash_with_indifferent_access.rb +0 -13
- data/test/support_test.rb +0 -1
- data/test/test_helper.rb +27 -8
- data/test/tools/auth_repl_set_manager.rb +14 -0
- data/test/tools/load.rb +58 -0
- data/test/tools/repl_set_manager.rb +34 -9
- data/test/tools/sharding_manager.rb +202 -0
- data/test/tools/test.rb +3 -12
- data/test/unit/collection_test.rb +20 -24
- data/test/unit/connection_test.rb +4 -18
- data/test/unit/cursor_test.rb +16 -6
- data/test/unit/db_test.rb +10 -11
- data/test/unit/repl_set_connection_test.rb +0 -23
- data/test/unit/safe_test.rb +3 -3
- data/test/uri_test.rb +91 -0
- metadata +49 -12
- data/docs/1.0_UPGRADE.md +0 -21
data/lib/mongo/util/core_ext.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
|
|
3
3
|
# --
|
|
4
|
-
# Copyright (C) 2008-
|
|
4
|
+
# Copyright (C) 2008-2011 10gen Inc.
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -48,3 +48,13 @@ class Hash
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
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
|
data/lib/mongo/util/pool.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
|
|
3
3
|
# --
|
|
4
|
-
# Copyright (C) 2008-
|
|
4
|
+
# Copyright (C) 2008-2011 10gen Inc.
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -22,14 +22,14 @@ module Mongo
|
|
|
22
22
|
|
|
23
23
|
# Create a new pool of connections.
|
|
24
24
|
#
|
|
25
|
-
def initialize(connection, host, port,
|
|
25
|
+
def initialize(connection, host, port, opts={})
|
|
26
26
|
@connection = connection
|
|
27
27
|
|
|
28
28
|
@host, @port = host, port
|
|
29
29
|
|
|
30
30
|
# Pool size and timeout.
|
|
31
|
-
@size =
|
|
32
|
-
@timeout =
|
|
31
|
+
@size = opts[:size] || 1
|
|
32
|
+
@timeout = opts[:timeout] || 5.0
|
|
33
33
|
|
|
34
34
|
# Mutex for synchronizing pool access
|
|
35
35
|
@connection_mutex = Mutex.new
|
|
@@ -37,7 +37,11 @@ module Mongo
|
|
|
37
37
|
# Condition variable for signal and wait
|
|
38
38
|
@queue = ConditionVariable.new
|
|
39
39
|
|
|
40
|
+
# Operations to perform on a socket
|
|
41
|
+
@socket_ops = Hash.new { |h, k| h[k] = [] }
|
|
42
|
+
|
|
40
43
|
@sockets = []
|
|
44
|
+
@pids = {}
|
|
41
45
|
@checked_out = []
|
|
42
46
|
end
|
|
43
47
|
|
|
@@ -46,12 +50,12 @@ module Mongo
|
|
|
46
50
|
begin
|
|
47
51
|
sock.close
|
|
48
52
|
rescue IOError => ex
|
|
49
|
-
warn "IOError when attempting to close socket connected "
|
|
50
|
-
+ "to #{@host}:#{@port}: #{ex.inspect}"
|
|
53
|
+
warn "IOError when attempting to close socket connected to #{@host}:#{@port}: #{ex.inspect}"
|
|
51
54
|
end
|
|
52
55
|
end
|
|
53
56
|
@host = @port = nil
|
|
54
57
|
@sockets.clear
|
|
58
|
+
@pids.clear
|
|
55
59
|
@checked_out.clear
|
|
56
60
|
end
|
|
57
61
|
|
|
@@ -73,21 +77,63 @@ module Mongo
|
|
|
73
77
|
socket = TCPSocket.new(@host, @port)
|
|
74
78
|
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
75
79
|
rescue => ex
|
|
76
|
-
raise ConnectionFailure, "Failed to connect
|
|
80
|
+
raise ConnectionFailure, "Failed to connect to host #{@host} and port #{@port}: #{ex}"
|
|
77
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
|
+
|
|
78
87
|
@sockets << socket
|
|
88
|
+
@pids[socket] = Process.pid
|
|
79
89
|
@checked_out << socket
|
|
80
90
|
socket
|
|
81
91
|
end
|
|
82
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
|
+
|
|
83
119
|
# Checks out the first available socket from the pool.
|
|
84
120
|
#
|
|
121
|
+
# If the pid has changed, remove the socket and check out
|
|
122
|
+
# new one.
|
|
123
|
+
#
|
|
85
124
|
# This method is called exclusively from #checkout;
|
|
86
125
|
# therefore, it runs within a mutex.
|
|
87
126
|
def checkout_existing_socket
|
|
88
127
|
socket = (@sockets - @checked_out).first
|
|
89
|
-
@
|
|
90
|
-
|
|
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
|
|
91
137
|
end
|
|
92
138
|
|
|
93
139
|
# Check out an existing socket or create a new socket if the maximum
|
|
@@ -110,14 +156,20 @@ module Mongo
|
|
|
110
156
|
checkout_new_socket
|
|
111
157
|
end
|
|
112
158
|
|
|
113
|
-
|
|
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
|
|
114
167
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
168
|
+
return socket
|
|
169
|
+
else
|
|
170
|
+
# Otherwise, wait
|
|
171
|
+
@queue.wait(@connection_mutex)
|
|
119
172
|
end
|
|
120
|
-
@queue.wait(@connection_mutex)
|
|
121
173
|
end
|
|
122
174
|
end
|
|
123
175
|
end
|
data/lib/mongo/util/support.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
|
|
3
3
|
# --
|
|
4
|
-
# Copyright (C) 2008-
|
|
4
|
+
# Copyright (C) 2008-2011 10gen Inc.
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -17,28 +17,101 @@
|
|
|
17
17
|
# ++
|
|
18
18
|
|
|
19
19
|
module Mongo
|
|
20
|
-
|
|
20
|
+
class URIParser
|
|
21
21
|
|
|
22
22
|
DEFAULT_PORT = 27017
|
|
23
|
-
MONGODB_URI_MATCHER = /(([
|
|
23
|
+
MONGODB_URI_MATCHER = /(([-.\w]+):([^@]+)@)?([-.\w]+)(:([\w]+))?(\/([-\w]+))?/
|
|
24
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]
|
|
25
27
|
|
|
26
|
-
|
|
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
|
|
27
56
|
|
|
28
57
|
# Parse a MongoDB URI. This method is used by Connection.from_uri.
|
|
29
58
|
# Returns an array of nodes and an array of db authorizations, if applicable.
|
|
30
59
|
#
|
|
31
|
-
#
|
|
32
|
-
|
|
60
|
+
# Note: passwords can contain any character except for a ','.
|
|
61
|
+
#
|
|
62
|
+
# @core connections
|
|
63
|
+
def initialize(string)
|
|
33
64
|
if string =~ /^mongodb:\/\//
|
|
34
65
|
string = string[10..-1]
|
|
35
66
|
else
|
|
36
67
|
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
|
37
68
|
end
|
|
38
69
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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(',')
|
|
42
115
|
specs.each do |spec|
|
|
43
116
|
matches = MONGODB_URI_MATCHER.match(spec)
|
|
44
117
|
if !matches
|
|
@@ -52,8 +125,8 @@ module Mongo
|
|
|
52
125
|
if !(port.to_s =~ /^\d+$/)
|
|
53
126
|
raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits."
|
|
54
127
|
end
|
|
55
|
-
port
|
|
56
|
-
db
|
|
128
|
+
port = port.to_i
|
|
129
|
+
db = matches[8]
|
|
57
130
|
|
|
58
131
|
if uname && pwd && db
|
|
59
132
|
auths << {'db_name' => db, 'username' => uname, 'password' => pwd}
|
|
@@ -62,10 +135,51 @@ module Mongo
|
|
|
62
135
|
"and db if any one of these is specified."
|
|
63
136
|
end
|
|
64
137
|
|
|
65
|
-
nodes << [host, port]
|
|
138
|
+
@nodes << [host, port]
|
|
66
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
|
|
67
149
|
|
|
68
|
-
|
|
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
|
|
69
183
|
end
|
|
70
184
|
end
|
|
71
185
|
end
|
data/lib/mongo.rb
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
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
|
+
# ++
|
|
2
18
|
|
|
3
19
|
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
4
20
|
|
|
5
21
|
module Mongo
|
|
6
|
-
VERSION = "1.
|
|
22
|
+
VERSION = "1.3.0"
|
|
7
23
|
end
|
|
8
24
|
|
|
9
25
|
module Mongo
|
|
@@ -11,6 +27,8 @@ module Mongo
|
|
|
11
27
|
DESCENDING = -1
|
|
12
28
|
GEO2D = '2d'
|
|
13
29
|
|
|
30
|
+
DEFAULT_MAX_BSON_SIZE = 4 * 1024 * 1024
|
|
31
|
+
|
|
14
32
|
module Constants
|
|
15
33
|
OP_REPLY = 1
|
|
16
34
|
OP_MSG = 1000
|
|
@@ -58,4 +76,22 @@ if RUBY_PLATFORM =~ /java/
|
|
|
58
76
|
end
|
|
59
77
|
require 'mongo/gridfs/grid_file_system'
|
|
60
78
|
|
|
61
|
-
|
|
79
|
+
# Use SystemTimer on Ruby 1.8
|
|
80
|
+
if !defined?(RUBY_ENGINE) || (RUBY_ENGINE == 'ruby' && RUBY_VERSION < '1.9.0')
|
|
81
|
+
begin
|
|
82
|
+
require 'system_timer'
|
|
83
|
+
if SystemTimer.method(:timeout).arity.abs != 2
|
|
84
|
+
raise LoadError
|
|
85
|
+
end
|
|
86
|
+
Mongo::TimeoutHandler = SystemTimer
|
|
87
|
+
rescue LoadError
|
|
88
|
+
warn "Could not load SystemTimer >= v1.2.0. Falling back to timeout.rb. " +
|
|
89
|
+
"SystemTimer is STRONGLY recommended for timeouts in Ruby 1.8.7. " +
|
|
90
|
+
"See http://ph7spot.com/blog/system-timer-1-2-release for details."
|
|
91
|
+
require 'timeout'
|
|
92
|
+
Mongo::TimeoutHandler = Timeout
|
|
93
|
+
end
|
|
94
|
+
else
|
|
95
|
+
require 'timeout'
|
|
96
|
+
Mongo::TimeoutHandler = Timeout
|
|
97
|
+
end
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
require 'test/test_helper'
|
|
2
|
+
|
|
3
|
+
class TestCollection < Test::Unit::TestCase
|
|
4
|
+
@@connection ||= Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost', ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT)
|
|
5
|
+
@@db = @@connection.db(MONGO_TEST_DB)
|
|
6
|
+
@@test = @@db.collection("test")
|
|
7
|
+
@@version = @@connection.server_version
|
|
8
|
+
|
|
9
|
+
def setup
|
|
10
|
+
@@test.remove
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def wait_for_async
|
|
14
|
+
sleep 0.2
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_async_update
|
|
18
|
+
id1 = @@test.save("x" => 5)
|
|
19
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
20
|
+
|
|
21
|
+
@@test.update({}, {"$inc" => {"x" => 1}}, :async => true) do |error, result|
|
|
22
|
+
assert_nil error
|
|
23
|
+
assert result
|
|
24
|
+
failsafe.call
|
|
25
|
+
end
|
|
26
|
+
wait_for_async
|
|
27
|
+
|
|
28
|
+
assert_equal 1, @@test.count()
|
|
29
|
+
assert_equal 6, @@test.find_one(:_id => id1)["x"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if @@version >= "1.1.3"
|
|
33
|
+
def test_async_multi_update
|
|
34
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
35
|
+
|
|
36
|
+
@@test.save("num" => 10)
|
|
37
|
+
@@test.save("num" => 10)
|
|
38
|
+
@@test.save("num" => 10)
|
|
39
|
+
assert_equal 3, @@test.count
|
|
40
|
+
|
|
41
|
+
@@test.update({"num" => 10}, {"$set" => {"num" => 100}}, :multi => true, :async => true) do |error, result|
|
|
42
|
+
assert_nil error
|
|
43
|
+
assert result
|
|
44
|
+
failsafe.call
|
|
45
|
+
end
|
|
46
|
+
wait_for_async
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_async_upsert
|
|
51
|
+
failsafe = mock('failsafe will get called in block')
|
|
52
|
+
failsafe.expects(:call).times(2)
|
|
53
|
+
|
|
54
|
+
@@test.update({"page" => "/"}, {"$inc" => {"count" => 1}}, :upsert => true, :async => true) do |error, result|
|
|
55
|
+
assert_nil error
|
|
56
|
+
assert result
|
|
57
|
+
failsafe.call
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@@test.update({"page" => "/"}, {"$inc" => {"count" => 1}}, :upsert => true, :async => true) do |error, result|
|
|
61
|
+
assert_nil error
|
|
62
|
+
assert result
|
|
63
|
+
failsafe.call
|
|
64
|
+
end
|
|
65
|
+
wait_for_async
|
|
66
|
+
|
|
67
|
+
assert_equal 1, @@test.count()
|
|
68
|
+
assert_equal 2, @@test.find_one()["count"]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_async_save
|
|
72
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
73
|
+
|
|
74
|
+
# note that the first parameter has explicit curly brackets around
|
|
75
|
+
# the hash; without those brackets as a delimiter, the :async key is
|
|
76
|
+
# viewed as part of the required +document+ parameter
|
|
77
|
+
@@test.save({"hello" => "world"}, :async => true) do |error, result|
|
|
78
|
+
assert_nil error
|
|
79
|
+
assert result
|
|
80
|
+
failsafe.call
|
|
81
|
+
end
|
|
82
|
+
wait_for_async
|
|
83
|
+
|
|
84
|
+
assert_equal "world", @@test.find_one()["hello"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_async_save_with_exception
|
|
88
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
89
|
+
|
|
90
|
+
@@test.create_index("hello", :unique => true)
|
|
91
|
+
@@test.save("hello" => "world")
|
|
92
|
+
|
|
93
|
+
# all async calls on collections occur in :safe mode
|
|
94
|
+
@@test.save({"hello" => "world"}, :async => true) do |error, result|
|
|
95
|
+
assert error
|
|
96
|
+
assert error.instance_of?(OperationFailure)
|
|
97
|
+
assert_nil result
|
|
98
|
+
failsafe.call
|
|
99
|
+
end
|
|
100
|
+
wait_for_async
|
|
101
|
+
|
|
102
|
+
assert_equal 1, @@test.count()
|
|
103
|
+
@@test.drop
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_async_remove
|
|
107
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
108
|
+
|
|
109
|
+
@conn = Connection.new
|
|
110
|
+
@db = @conn[MONGO_TEST_DB]
|
|
111
|
+
@test = @db['test-async-remove']
|
|
112
|
+
@test.save({:a => 50})
|
|
113
|
+
@test.remove({}, :async => true) do |error, result|
|
|
114
|
+
assert_nil error
|
|
115
|
+
assert result
|
|
116
|
+
failsafe.call
|
|
117
|
+
end
|
|
118
|
+
wait_for_async
|
|
119
|
+
|
|
120
|
+
@test.drop
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_async_count
|
|
124
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
125
|
+
|
|
126
|
+
@@test.drop
|
|
127
|
+
|
|
128
|
+
@@test.save("x" => 1)
|
|
129
|
+
@@test.save("x" => 2)
|
|
130
|
+
|
|
131
|
+
@@test.count(:async => true) do |error, result|
|
|
132
|
+
assert_nil error
|
|
133
|
+
assert_equal 2, result
|
|
134
|
+
failsafe.call
|
|
135
|
+
end
|
|
136
|
+
wait_for_async
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Note: #size is just an alias for #count.
|
|
140
|
+
def test_async_size
|
|
141
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
142
|
+
|
|
143
|
+
@@test.drop
|
|
144
|
+
|
|
145
|
+
@@test.save("x" => 1)
|
|
146
|
+
@@test.save("x" => 2)
|
|
147
|
+
|
|
148
|
+
@@test.size(:async => true) do |error, result|
|
|
149
|
+
assert_nil error
|
|
150
|
+
assert_equal 2, result
|
|
151
|
+
failsafe.call
|
|
152
|
+
end
|
|
153
|
+
wait_for_async
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_async_find_one
|
|
157
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
158
|
+
|
|
159
|
+
id = @@test.save("hello" => "world", "foo" => "bar")
|
|
160
|
+
|
|
161
|
+
@@test.find_one({}, :async => true) do |error, result|
|
|
162
|
+
assert_nil error
|
|
163
|
+
assert_equal @@test.find_one(id), result
|
|
164
|
+
failsafe.call
|
|
165
|
+
end
|
|
166
|
+
wait_for_async
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def test_async_insert
|
|
170
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
171
|
+
|
|
172
|
+
doc = {"hello" => "world"}
|
|
173
|
+
@@test.insert(doc, :async => true) do |error, result|
|
|
174
|
+
assert_nil error
|
|
175
|
+
assert result
|
|
176
|
+
failsafe.call
|
|
177
|
+
end
|
|
178
|
+
wait_for_async
|
|
179
|
+
|
|
180
|
+
assert_equal 1, @@test.count
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def test_async_find
|
|
184
|
+
assert_raise RuntimeError do
|
|
185
|
+
@@test.find({}, :async => true)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
if @@version > "1.3.0"
|
|
190
|
+
def test_async_find_and_modify
|
|
191
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
192
|
+
|
|
193
|
+
@@test << { :a => 1, :processed => false }
|
|
194
|
+
@@test << { :a => 2, :processed => false }
|
|
195
|
+
@@test << { :a => 3, :processed => false }
|
|
196
|
+
|
|
197
|
+
@@test.find_and_modify(:query => {}, :sort => [['a', -1]], :update => {"$set" => {:processed => true}}, :async => true) do |error, result|
|
|
198
|
+
assert_nil error
|
|
199
|
+
assert result
|
|
200
|
+
failsafe.call
|
|
201
|
+
end
|
|
202
|
+
wait_for_async
|
|
203
|
+
|
|
204
|
+
assert @@test.find_one({:a => 3})['processed']
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def test_async_find_and_modify_with_invalid_options
|
|
208
|
+
failsafe = mock('failsafe will get called in block', :call => true)
|
|
209
|
+
|
|
210
|
+
@@test << { :a => 1, :processed => false }
|
|
211
|
+
@@test << { :a => 2, :processed => false }
|
|
212
|
+
@@test << { :a => 3, :processed => false }
|
|
213
|
+
|
|
214
|
+
@@test.find_and_modify(:blimey => {}, :async => true) do |error, result|
|
|
215
|
+
assert error
|
|
216
|
+
assert error.instance_of?(OperationFailure)
|
|
217
|
+
assert_nil result
|
|
218
|
+
failsafe.call
|
|
219
|
+
end
|
|
220
|
+
wait_for_async
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'test/test_helper'
|
|
2
|
+
include Mongo
|
|
3
|
+
|
|
4
|
+
class ConnectionTest < Test::Unit::TestCase
|
|
5
|
+
context "Initialization: " do
|
|
6
|
+
|
|
7
|
+
context "given async connection options" do
|
|
8
|
+
|
|
9
|
+
should "default the workers pool to 1" do
|
|
10
|
+
Async::WorkerPool.expects(:new).with(1)
|
|
11
|
+
|
|
12
|
+
Connection.new('localhost', 27017)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
should "override the workers pool size with the :worker_pool_size key" do
|
|
16
|
+
size = 6
|
|
17
|
+
Async::WorkerPool.expects(:new).with(size)
|
|
18
|
+
|
|
19
|
+
Connection.new('localhost', 27017, :worker_pool_size => size)
|
|
20
|
+
end
|
|
21
|
+
end # context 'given async connection options'
|
|
22
|
+
|
|
23
|
+
end # context 'Initialization'
|
|
24
|
+
end
|