mongo 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +15 -3
- data/docs/FAQ.md +4 -0
- data/docs/HISTORY.md +11 -4
- data/lib/mongo.rb +1 -1
- data/lib/mongo/collection.rb +28 -25
- data/lib/mongo/connection.rb +54 -12
- data/lib/mongo/cursor.rb +17 -11
- data/lib/mongo/db.rb +31 -11
- data/lib/mongo/repl_set_connection.rb +17 -2
- data/lib/mongo/util/pool.rb +50 -6
- 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 +43 -0
- data/test/connection_test.rb +1 -1
- data/test/db_api_test.rb +0 -37
- 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/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 +19 -22
- data/test/unit/connection_test.rb +0 -1
- data/test/unit/db_test.rb +1 -0
- metadata +23 -11
data/README.md
CHANGED
@@ -129,7 +129,7 @@ The driver is thread-safe.
|
|
129
129
|
|
130
130
|
## Connection Pooling
|
131
131
|
|
132
|
-
|
132
|
+
The driver implements connection pooling. By default, only one
|
133
133
|
socket connection will be opened to MongoDB. However, if you're running a
|
134
134
|
multi-threaded application, you can specify a maximum pool size and a maximum
|
135
135
|
timeout for waiting for old connections to be released to the pool.
|
@@ -149,12 +149,24 @@ processes will create a new connection to the database. In Passenger, this can b
|
|
149
149
|
if defined?(PhusionPassenger)
|
150
150
|
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
151
151
|
if forked
|
152
|
-
#
|
152
|
+
# Reset all connection objects. How you do this depends on where
|
153
|
+
# you keep your connection object. In any case, call the #connect
|
154
|
+
# method on the connection object. For example:
|
155
|
+
# CONN.connect
|
156
|
+
#
|
157
|
+
# If you're using MongoMapper:
|
158
|
+
# MongoMapper.connection.connect
|
153
159
|
end
|
154
160
|
end
|
155
161
|
end
|
156
162
|
|
157
|
-
|
163
|
+
In Unicorn, add this to your unicorn.rb file:
|
164
|
+
|
165
|
+
after_fork do |server, worker|
|
166
|
+
# Handle reconnection
|
167
|
+
end
|
168
|
+
|
169
|
+
The above code should be put into a Rails initializer or similar initialization script.
|
158
170
|
|
159
171
|
## String Encoding
|
160
172
|
|
data/docs/FAQ.md
CHANGED
@@ -110,3 +110,7 @@ Without further investigation, it's impossible to know exactly what has caused t
|
|
110
110
|
Because of the indeterminacy involved, the MongoDB drivers will not retry operations on connection failure. How connection failures should be handled is entirely dependent on the application. Therefore, we leave it to the application developers to make the best decision in this case.
|
111
111
|
|
112
112
|
The drivers will reconnect on the subsequent operation.
|
113
|
+
|
114
|
+
#### I ocassionally get an error saying that responses are out of order. What's happening?
|
115
|
+
|
116
|
+
See (this JIRA issue)[http://jira.mongodb.org/browse/RUBY-221].
|
data/docs/HISTORY.md
CHANGED
@@ -1,14 +1,21 @@
|
|
1
1
|
# MongoDB Ruby Driver History
|
2
2
|
|
3
|
-
1.2.
|
3
|
+
### 1.2.1
|
4
4
|
2011-1-18
|
5
|
+
|
6
|
+
* Enable authentication with connection pooling.
|
7
|
+
* Allow custom logging with Connection#instrument (CodeMonkeySteve)
|
8
|
+
* Minor fixes and doc improvements.
|
9
|
+
|
10
|
+
### 1.2.0
|
11
|
+
2011-1-18
|
12
|
+
|
5
13
|
* Some minor improvements. See commit history.
|
6
|
-
* Since nothing major was reported for the RC, we're releasing.
|
7
14
|
|
8
|
-
1.2.rc0
|
15
|
+
### 1.2.rc0
|
9
16
|
2011-1-5
|
10
17
|
|
11
|
-
Lots of
|
18
|
+
Lots of cleanup and minor bug fixes.
|
12
19
|
* Issues resolved: http://jira.mongodb.org/browse/RUBY/fixforversion/10222
|
13
20
|
* Updated Java BSON to Java driver 2.4.
|
14
21
|
* Platform gem for JRuby bson.
|
data/lib/mongo.rb
CHANGED
data/lib/mongo/collection.rb
CHANGED
@@ -79,7 +79,6 @@ module Mongo
|
|
79
79
|
|
80
80
|
@db, @name = db, name
|
81
81
|
@connection = @db.connection
|
82
|
-
@logger = @connection.logger
|
83
82
|
@cache_time = @db.cache_time
|
84
83
|
@cache = Hash.new(0)
|
85
84
|
unless pk_factory
|
@@ -322,12 +321,13 @@ module Mongo
|
|
322
321
|
message.put_int(0)
|
323
322
|
message.put_binary(BSON::BSON_CODER.serialize(selector, false, true).to_s)
|
324
323
|
|
325
|
-
@
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
324
|
+
@connection.instrument(:remove, :database => @db.name, :collection => @name, :selector => selector) do
|
325
|
+
if safe
|
326
|
+
@connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name, nil, safe)
|
327
|
+
else
|
328
|
+
@connection.send_message(Mongo::Constants::OP_DELETE, message)
|
329
|
+
true
|
330
|
+
end
|
331
331
|
end
|
332
332
|
end
|
333
333
|
|
@@ -367,11 +367,13 @@ module Mongo
|
|
367
367
|
message.put_int(update_options)
|
368
368
|
message.put_binary(BSON::BSON_CODER.serialize(selector, false, true).to_s)
|
369
369
|
message.put_binary(BSON::BSON_CODER.serialize(document, false, true).to_s)
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
370
|
+
|
371
|
+
@connection.instrument(:update, :database => @db.name, :collection => @name, :selector => selector, :document => document) do
|
372
|
+
if safe
|
373
|
+
@connection.send_message_with_safe_check(Mongo::Constants::OP_UPDATE, message, @db.name, nil, safe)
|
374
|
+
else
|
375
|
+
@connection.send_message(Mongo::Constants::OP_UPDATE, message, nil)
|
376
|
+
end
|
375
377
|
end
|
376
378
|
end
|
377
379
|
|
@@ -570,9 +572,9 @@ module Mongo
|
|
570
572
|
# set to true.
|
571
573
|
#
|
572
574
|
# @return [Array] the command response consisting of grouped items.
|
573
|
-
def group(
|
574
|
-
if
|
575
|
-
return new_group(
|
575
|
+
def group(opts, condition={}, initial={}, reduce=nil, finalize=nil)
|
576
|
+
if opts.is_a?(Hash)
|
577
|
+
return new_group(opts)
|
576
578
|
else
|
577
579
|
warn "Collection#group no longer take a list of paramters. This usage is deprecated." +
|
578
580
|
"Check out the new API at http://api.mongodb.org/ruby/current/Mongo/Collection.html#group-instance_method"
|
@@ -589,19 +591,19 @@ module Mongo
|
|
589
591
|
}
|
590
592
|
}
|
591
593
|
|
592
|
-
if
|
594
|
+
if opts.is_a?(Symbol)
|
593
595
|
raise MongoArgumentError, "Group takes either an array of fields to group by or a JavaScript function" +
|
594
596
|
"in the form of a String or BSON::Code."
|
595
597
|
end
|
596
598
|
|
597
|
-
unless
|
598
|
-
if
|
599
|
+
unless opts.nil?
|
600
|
+
if opts.is_a? Array
|
599
601
|
key_type = "key"
|
600
602
|
key_value = {}
|
601
|
-
|
603
|
+
opts.each { |k| key_value[k] = 1 }
|
602
604
|
else
|
603
605
|
key_type = "$keyf"
|
604
|
-
key_value =
|
606
|
+
key_value = opts.is_a?(BSON::Code) ? opts : BSON::Code.new(opts)
|
605
607
|
end
|
606
608
|
|
607
609
|
group_command["group"][key_type] = key_value
|
@@ -838,11 +840,12 @@ module Mongo
|
|
838
840
|
end
|
839
841
|
raise InvalidOperation, "Exceded maximum insert size of 16,000,000 bytes" if message.size > 16_000_000
|
840
842
|
|
841
|
-
@
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
843
|
+
@connection.instrument(:insert, :database => @db.name, :collection => collection_name, :documents => documents) do
|
844
|
+
if safe
|
845
|
+
@connection.send_message_with_safe_check(Mongo::Constants::OP_INSERT, message, @db.name, nil, safe)
|
846
|
+
else
|
847
|
+
@connection.send_message(Mongo::Constants::OP_INSERT, message, nil)
|
848
|
+
end
|
846
849
|
end
|
847
850
|
documents.collect { |o| o[:_id] || o['_id'] }
|
848
851
|
end
|
data/lib/mongo/connection.rb
CHANGED
@@ -35,7 +35,7 @@ module Mongo
|
|
35
35
|
STANDARD_HEADER_SIZE = 16
|
36
36
|
RESPONSE_HEADER_SIZE = 20
|
37
37
|
|
38
|
-
attr_reader :logger, :size, :auths, :primary, :safe, :primary_pool, :host_to_try
|
38
|
+
attr_reader :logger, :size, :auths, :primary, :safe, :primary_pool, :host_to_try, :pool_size
|
39
39
|
|
40
40
|
# Counter for generating unique request ids.
|
41
41
|
@@current_request_id = 0
|
@@ -61,7 +61,8 @@ module Mongo
|
|
61
61
|
# on initialization.
|
62
62
|
# @option opts [Boolean] :slave_ok (false) Must be set to +true+ when connecting
|
63
63
|
# to a single, slave node.
|
64
|
-
# @option opts [Logger, #debug] :logger (nil) Logger instance
|
64
|
+
# @option opts [Logger, #debug] :logger (nil) A Logger instance for debugging driver ops. Note that
|
65
|
+
# logging negatively impacts performance; therefore, it should not be used for high-performance apps.
|
65
66
|
# @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
|
66
67
|
# connection pool. Note: this setting is relevant only for multi-threaded applications.
|
67
68
|
# @option opts [Float] :timeout (5.0) When all of the connections a pool are checked out,
|
@@ -188,10 +189,11 @@ module Mongo
|
|
188
189
|
#
|
189
190
|
# @raise [AuthenticationError] raises an exception if any one
|
190
191
|
# authentication fails.
|
191
|
-
def apply_saved_authentication
|
192
|
+
def apply_saved_authentication(opts={})
|
192
193
|
return false if @auths.empty?
|
193
194
|
@auths.each do |auth|
|
194
|
-
self[auth['db_name']].
|
195
|
+
self[auth['db_name']].issue_authentication(auth['username'], auth['password'], false,
|
196
|
+
:socket => opts[:socket])
|
195
197
|
end
|
196
198
|
true
|
197
199
|
end
|
@@ -241,6 +243,14 @@ module Mongo
|
|
241
243
|
true
|
242
244
|
end
|
243
245
|
|
246
|
+
def authenticate_pools
|
247
|
+
@primary_pool.authenticate_existing
|
248
|
+
end
|
249
|
+
|
250
|
+
def logout_pools(db)
|
251
|
+
@primary_pool.logout_existing(db)
|
252
|
+
end
|
253
|
+
|
244
254
|
# Return a hash with all database names
|
245
255
|
# and their respective sizes on disk.
|
246
256
|
#
|
@@ -411,7 +421,13 @@ module Mongo
|
|
411
421
|
request_id = add_message_headers(message, operation)
|
412
422
|
packed_message = message.to_s
|
413
423
|
begin
|
414
|
-
|
424
|
+
if socket
|
425
|
+
sock = socket
|
426
|
+
checkin = false
|
427
|
+
else
|
428
|
+
sock = (command ? checkout_writer : checkout_reader)
|
429
|
+
checkin = true
|
430
|
+
end
|
415
431
|
|
416
432
|
result = ''
|
417
433
|
@safe_mutexes[sock].synchronize do
|
@@ -419,7 +435,9 @@ module Mongo
|
|
419
435
|
result = receive(sock, request_id)
|
420
436
|
end
|
421
437
|
ensure
|
422
|
-
|
438
|
+
if checkin
|
439
|
+
command ? checkin_writer(sock) : checkin_reader(sock)
|
440
|
+
end
|
423
441
|
end
|
424
442
|
result
|
425
443
|
end
|
@@ -451,6 +469,7 @@ module Mongo
|
|
451
469
|
raise ConnectionFailure, "Failed to connect to a master node at #{@host_to_try[0]}:#{@host_to_try[1]}"
|
452
470
|
end
|
453
471
|
end
|
472
|
+
alias :reconnect :connect
|
454
473
|
|
455
474
|
def connecting?
|
456
475
|
@nodes_to_try.length > 0
|
@@ -517,6 +536,15 @@ module Mongo
|
|
517
536
|
end
|
518
537
|
end
|
519
538
|
|
539
|
+
# Execute the block and log the operation described by name
|
540
|
+
# and payload.
|
541
|
+
# TODO: Not sure if this should take a block.
|
542
|
+
def instrument(name, payload = {}, &blk)
|
543
|
+
res = yield
|
544
|
+
log_operation(name, payload)
|
545
|
+
res
|
546
|
+
end
|
547
|
+
|
520
548
|
protected
|
521
549
|
|
522
550
|
# Generic initialization code.
|
@@ -548,7 +576,12 @@ module Mongo
|
|
548
576
|
@primary = nil
|
549
577
|
@primary_pool = nil
|
550
578
|
|
551
|
-
@logger
|
579
|
+
@logger = opts[:logger] || nil
|
580
|
+
|
581
|
+
if @logger
|
582
|
+
@logger.debug("MongoDB logging. Please note that logging negatively impacts performance " +
|
583
|
+
"and should be disabled for high-performance production apps.")
|
584
|
+
end
|
552
585
|
|
553
586
|
should_connect = opts.fetch(:connect, true)
|
554
587
|
connect if should_connect
|
@@ -570,6 +603,18 @@ module Mongo
|
|
570
603
|
end
|
571
604
|
end
|
572
605
|
|
606
|
+
## Logging methods
|
607
|
+
|
608
|
+
def log_operation(name, payload)
|
609
|
+
return unless @logger
|
610
|
+
msg = "#{payload[:database]}['#{payload[:collection]}'].#{name}("
|
611
|
+
msg += payload.values_at(:selector, :document, :documents, :fields ).compact.map(&:inspect).join(', ') + ")"
|
612
|
+
msg += ".skip(#{payload[:skip]})" if payload[:skip]
|
613
|
+
msg += ".limit(#{payload[:limit]})" if payload[:limit]
|
614
|
+
msg += ".sort(#{payload[:sort]})" if payload[:sort]
|
615
|
+
@logger.debug "MONGODB #{msg}"
|
616
|
+
end
|
617
|
+
|
573
618
|
private
|
574
619
|
|
575
620
|
## Methods for establishing a connection:
|
@@ -588,7 +633,7 @@ module Mongo
|
|
588
633
|
socket = TCPSocket.new(host, port)
|
589
634
|
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
590
635
|
|
591
|
-
config = self['admin'].command({:ismaster => 1}, :
|
636
|
+
config = self['admin'].command({:ismaster => 1}, :socket => socket)
|
592
637
|
rescue OperationFailure, SocketError, SystemCallError, IOError => ex
|
593
638
|
close
|
594
639
|
ensure
|
@@ -598,16 +643,13 @@ module Mongo
|
|
598
643
|
config
|
599
644
|
end
|
600
645
|
|
601
|
-
# Set the specified node as primary
|
602
|
-
# apply any saved authentication credentials.
|
646
|
+
# Set the specified node as primary.
|
603
647
|
def set_primary(node)
|
604
648
|
host, port = *node
|
605
649
|
@primary = [host, port]
|
606
650
|
@primary_pool = Pool.new(self, host, port, :size => @pool_size, :timeout => @timeout)
|
607
|
-
apply_saved_authentication
|
608
651
|
end
|
609
652
|
|
610
|
-
|
611
653
|
## Low-level connection methods.
|
612
654
|
|
613
655
|
def receive(sock, expected_response)
|
data/lib/mongo/cursor.rb
CHANGED
@@ -373,18 +373,21 @@ module Mongo
|
|
373
373
|
end
|
374
374
|
|
375
375
|
# Run query the first time we request an object from the wire
|
376
|
+
# TODO: should we be calling instrument_payload even if logging
|
377
|
+
# is disabled?
|
376
378
|
def send_initial_query
|
377
379
|
if @query_run
|
378
380
|
false
|
379
381
|
else
|
380
382
|
message = construct_query_message
|
381
|
-
@
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
383
|
+
@connection.instrument(:find, instrument_payload) do
|
384
|
+
results, @n_received, @cursor_id = @connection.receive_message(
|
385
|
+
Mongo::Constants::OP_QUERY, message, nil, @socket, @command)
|
386
|
+
@returned += @n_received
|
387
|
+
@cache += results
|
388
|
+
@query_run = true
|
389
|
+
close_cursor_if_query_complete
|
390
|
+
end
|
388
391
|
true
|
389
392
|
end
|
390
393
|
end
|
@@ -401,10 +404,13 @@ module Mongo
|
|
401
404
|
message
|
402
405
|
end
|
403
406
|
|
404
|
-
def
|
405
|
-
|
406
|
-
|
407
|
-
|
407
|
+
def instrument_payload
|
408
|
+
log = { :database => @db.name, :collection => @collection.name, :selector => selector }
|
409
|
+
log[:fields] = @fields if @fields
|
410
|
+
log[:skip] = @skip if @skip && (@skip > 0)
|
411
|
+
log[:limit] = @limit if @limit && (@limit > 0)
|
412
|
+
log[:order] = @order if @order
|
413
|
+
log
|
408
414
|
end
|
409
415
|
|
410
416
|
def construct_query_spec
|
data/lib/mongo/db.rb
CHANGED
@@ -92,7 +92,8 @@ module Mongo
|
|
92
92
|
# @param [String] password
|
93
93
|
# @param [Boolean] save_auth
|
94
94
|
# Save this authentication to the connection object using Connection#add_auth. This
|
95
|
-
# will ensure that the authentication will be applied on database reconnect.
|
95
|
+
# will ensure that the authentication will be applied on database reconnect. Note
|
96
|
+
# that this value must be true when using connection pooling.
|
96
97
|
#
|
97
98
|
# @return [Boolean]
|
98
99
|
#
|
@@ -100,8 +101,19 @@ module Mongo
|
|
100
101
|
#
|
101
102
|
# @core authenticate authenticate-instance_method
|
102
103
|
def authenticate(username, password, save_auth=true)
|
103
|
-
|
104
|
-
|
104
|
+
if @connection.pool_size > 1
|
105
|
+
if !save_auth
|
106
|
+
raise MongoArgumentError, "If using connection pooling, :save_auth must be set to true."
|
107
|
+
end
|
108
|
+
@connection.authenticate_pools
|
109
|
+
end
|
110
|
+
|
111
|
+
issue_authentication(username, password, save_auth)
|
112
|
+
end
|
113
|
+
|
114
|
+
def issue_authentication(username, password, save_auth=true, opts={})
|
115
|
+
doc = command({:getnonce => 1}, :check_response => false, :socket => opts[:socket])
|
116
|
+
raise MongoDBError, "Error retrieving nonce: #{doc}" unless ok?(doc)
|
105
117
|
nonce = doc['nonce']
|
106
118
|
|
107
119
|
auth = BSON::OrderedHash.new
|
@@ -109,7 +121,7 @@ module Mongo
|
|
109
121
|
auth['user'] = username
|
110
122
|
auth['nonce'] = nonce
|
111
123
|
auth['key'] = Mongo::Support.auth_key(username, password, nonce)
|
112
|
-
if ok?(self.command(auth, :check_response => false))
|
124
|
+
if ok?(self.command(auth, :check_response => false, :socket => opts[:socket]))
|
113
125
|
if save_auth
|
114
126
|
@connection.add_auth(@name, username, password)
|
115
127
|
end
|
@@ -121,7 +133,7 @@ module Mongo
|
|
121
133
|
|
122
134
|
# Adds a stored Javascript function to the database which can executed
|
123
135
|
# server-side in map_reduce, db.eval and $where clauses.
|
124
|
-
#
|
136
|
+
#
|
125
137
|
# @param [String] function_name
|
126
138
|
# @param [String] code
|
127
139
|
#
|
@@ -179,14 +191,22 @@ module Mongo
|
|
179
191
|
end
|
180
192
|
|
181
193
|
# Deauthorizes use for this database for this connection. Also removes
|
182
|
-
# any saved
|
194
|
+
# any saved authentication in the connection class associated with this
|
183
195
|
# database.
|
184
196
|
#
|
185
197
|
# @raise [MongoDBError] if logging out fails.
|
186
198
|
#
|
187
199
|
# @return [Boolean]
|
188
|
-
def logout
|
189
|
-
|
200
|
+
def logout(opts={})
|
201
|
+
if @connection.pool_size > 1
|
202
|
+
@connection.logout_pools(@name)
|
203
|
+
end
|
204
|
+
|
205
|
+
issue_logout(opts)
|
206
|
+
end
|
207
|
+
|
208
|
+
def issue_logout(opts={})
|
209
|
+
doc = command({:logout => 1}, :socket => opts[:socket])
|
190
210
|
if ok?(doc)
|
191
211
|
@connection.remove_auth(@name)
|
192
212
|
true
|
@@ -455,14 +475,14 @@ module Mongo
|
|
455
475
|
#
|
456
476
|
# @option opts [Boolean] :check_response (true) If +true+, raises an exception if the
|
457
477
|
# command fails.
|
458
|
-
# @option opts [Socket] :
|
478
|
+
# @option opts [Socket] :socket a socket to use for sending the command. This is mainly for internal use.
|
459
479
|
#
|
460
480
|
# @return [Hash]
|
461
481
|
#
|
462
482
|
# @core commands command_instance-method
|
463
483
|
def command(selector, opts={})
|
464
484
|
check_response = opts.fetch(:check_response, true)
|
465
|
-
|
485
|
+
socket = opts[:socket]
|
466
486
|
raise MongoArgumentError, "command must be given a selector" unless selector.is_a?(Hash) && !selector.empty?
|
467
487
|
if selector.keys.length > 1 && RUBY_VERSION < '1.9' && selector.class != BSON::OrderedHash
|
468
488
|
raise MongoArgumentError, "DB#command requires an OrderedHash when hash contains multiple keys"
|
@@ -470,7 +490,7 @@ module Mongo
|
|
470
490
|
|
471
491
|
begin
|
472
492
|
result = Cursor.new(system_command_collection,
|
473
|
-
:limit => -1, :selector => selector, :socket =>
|
493
|
+
:limit => -1, :selector => selector, :socket => socket).next_document
|
474
494
|
rescue OperationFailure => ex
|
475
495
|
raise OperationFailure, "Database command '#{selector.keys.first}' failed: #{ex.message}"
|
476
496
|
end
|