mongo 0.17.1 → 0.18
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +69 -47
- data/Rakefile +26 -0
- data/lib/mongo.rb +2 -2
- data/lib/mongo/admin.rb +3 -3
- data/lib/mongo/collection.rb +51 -29
- data/lib/mongo/connection.rb +476 -94
- data/lib/mongo/cursor.rb +19 -17
- data/lib/mongo/db.rb +52 -324
- data/lib/mongo/errors.rb +9 -0
- data/lib/mongo/gridfs/grid_store.rb +1 -1
- data/lib/mongo/util/conversions.rb +4 -36
- data/test/replica/count_test.rb +34 -0
- data/test/replica/insert_test.rb +50 -0
- data/test/replica/pooled_insert_test.rb +54 -0
- data/test/replica/query_test.rb +39 -0
- data/test/test_collection.rb +23 -10
- data/test/test_connection.rb +19 -20
- data/test/test_conversions.rb +2 -2
- data/test/test_cursor.rb +18 -18
- data/test/test_db.rb +26 -35
- data/test/test_db_api.rb +9 -30
- data/test/test_helper.rb +15 -0
- data/test/test_slave_connection.rb +5 -4
- data/test/test_threading.rb +2 -2
- data/test/threading/test_threading_large_pool.rb +90 -0
- data/test/unit/collection_test.rb +13 -15
- data/test/unit/connection_test.rb +122 -0
- data/test/unit/cursor_test.rb +4 -32
- data/test/unit/db_test.rb +4 -32
- metadata +8 -2
data/lib/mongo/cursor.rb
CHANGED
@@ -32,6 +32,7 @@ module Mongo
|
|
32
32
|
def initialize(collection, options={})
|
33
33
|
@db = collection.db
|
34
34
|
@collection = collection
|
35
|
+
@connection = @db.connection
|
35
36
|
|
36
37
|
@selector = convert_selector_for_query(options[:selector])
|
37
38
|
@fields = convert_fields_for_query(options[:fields])
|
@@ -43,8 +44,9 @@ module Mongo
|
|
43
44
|
@snapshot = options[:snapshot]
|
44
45
|
@timeout = options[:timeout] || false
|
45
46
|
@explain = options[:explain]
|
47
|
+
@socket = options[:socket]
|
46
48
|
|
47
|
-
@full_collection_name
|
49
|
+
@full_collection_name = "#{@collection.db.name}.#{@collection.name}"
|
48
50
|
@cache = []
|
49
51
|
@closed = false
|
50
52
|
@query_run = false
|
@@ -63,9 +65,12 @@ module Mongo
|
|
63
65
|
# pair but it has died or something like that) then we close that
|
64
66
|
# connection. If the db has auto connect option and a pair of
|
65
67
|
# servers, next request will re-open on master server.
|
66
|
-
|
68
|
+
if err == "not master"
|
69
|
+
raise ConnectionFailure, err
|
70
|
+
@connection.close
|
71
|
+
end
|
67
72
|
|
68
|
-
raise err
|
73
|
+
raise OperationFailure, err
|
69
74
|
end
|
70
75
|
|
71
76
|
o
|
@@ -80,7 +85,7 @@ module Mongo
|
|
80
85
|
command = OrderedHash["count", @collection.name,
|
81
86
|
"query", @selector,
|
82
87
|
"fields", @fields]
|
83
|
-
response = @db.
|
88
|
+
response = @db.command(command)
|
84
89
|
return response['n'].to_i if response['ok'] == 1
|
85
90
|
return 0 if response['errmsg'] == "ns missing"
|
86
91
|
raise OperationFailure, "Count failed: #{response['errmsg']}"
|
@@ -196,7 +201,7 @@ module Mongo
|
|
196
201
|
message.put_int(0)
|
197
202
|
message.put_int(1)
|
198
203
|
message.put_long(@cursor_id)
|
199
|
-
@
|
204
|
+
@connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, "cursor.close()")
|
200
205
|
end
|
201
206
|
@cursor_id = 0
|
202
207
|
@closed = true
|
@@ -209,7 +214,7 @@ module Mongo
|
|
209
214
|
# See http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
|
210
215
|
def query_opts
|
211
216
|
timeout = @timeout ? 0 : Mongo::Constants::OP_QUERY_NO_CURSOR_TIMEOUT
|
212
|
-
slave_ok = @
|
217
|
+
slave_ok = @connection.slave_ok? ? Mongo::Constants::OP_QUERY_SLAVE_OK : 0
|
213
218
|
slave_ok + timeout
|
214
219
|
end
|
215
220
|
|
@@ -291,19 +296,19 @@ module Mongo
|
|
291
296
|
|
292
297
|
# Cursor id.
|
293
298
|
message.put_long(@cursor_id)
|
294
|
-
results, @n_received, @cursor_id = @
|
299
|
+
results, @n_received, @cursor_id = @connection.receive_message(Mongo::Constants::OP_GET_MORE, message, "cursor.get_more()", @socket)
|
295
300
|
@cache += results
|
296
301
|
close_cursor_if_query_complete
|
297
302
|
end
|
298
303
|
|
299
|
-
# Run query first time we request an object from the wire
|
304
|
+
# Run query the first time we request an object from the wire
|
300
305
|
def send_query_if_needed
|
301
306
|
if @query_run
|
302
307
|
false
|
303
308
|
else
|
304
309
|
message = construct_query_message
|
305
|
-
results, @n_received, @cursor_id = @
|
306
|
-
(query_log_message if @
|
310
|
+
results, @n_received, @cursor_id = @connection.receive_message(Mongo::Constants::OP_QUERY, message,
|
311
|
+
(query_log_message if @connection.logger), @socket)
|
307
312
|
@cache += results
|
308
313
|
@query_run = true
|
309
314
|
close_cursor_if_query_complete
|
@@ -344,14 +349,11 @@ module Mongo
|
|
344
349
|
|
345
350
|
def formatted_order_clause
|
346
351
|
case @order
|
347
|
-
when String then string_as_sort_parameters(@order)
|
348
|
-
when
|
349
|
-
when Array then array_as_sort_parameters(@order)
|
350
|
-
when Hash # Should be an ordered hash, but this message doesn't care
|
351
|
-
warn_if_deprecated(@order)
|
352
|
-
@order
|
352
|
+
when String, Symbol then string_as_sort_parameters(@order)
|
353
|
+
when Array then array_as_sort_parameters(@order)
|
353
354
|
else
|
354
|
-
raise InvalidSortValueError, "Illegal
|
355
|
+
raise InvalidSortValueError, "Illegal sort clause, '#{@order.class.name}'; must be of the form " +
|
356
|
+
"[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
|
355
357
|
end
|
356
358
|
end
|
357
359
|
|
data/lib/mongo/db.rb
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
# ++
|
16
16
|
|
17
17
|
require 'socket'
|
18
|
+
require 'timeout'
|
18
19
|
require 'digest/md5'
|
19
20
|
require 'thread'
|
20
21
|
require 'mongo/collection'
|
@@ -26,8 +27,6 @@ module Mongo
|
|
26
27
|
# A Mongo database.
|
27
28
|
class DB
|
28
29
|
|
29
|
-
STANDARD_HEADER_SIZE = 16
|
30
|
-
RESPONSE_HEADER_SIZE = 20
|
31
30
|
SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
|
32
31
|
SYSTEM_INDEX_COLLECTION = "system.indexes"
|
33
32
|
SYSTEM_PROFILE_COLLECTION = "system.profile"
|
@@ -38,11 +37,10 @@ module Mongo
|
|
38
37
|
@@current_request_id = 0
|
39
38
|
|
40
39
|
# Strict mode enforces collection existence checks. When +true+,
|
41
|
-
# asking for a collection that does not exist or trying to create a
|
42
|
-
# collection that already exists raises an error.
|
40
|
+
# asking for a collection that does not exist, or trying to create a
|
41
|
+
# collection that already exists, raises an error.
|
43
42
|
#
|
44
|
-
# Strict mode is
|
45
|
-
# any time.
|
43
|
+
# Strict mode is disabled by default, but enabled (+true+) at any time.
|
46
44
|
attr_writer :strict
|
47
45
|
|
48
46
|
# Returns the value of the +strict+ flag.
|
@@ -51,27 +49,16 @@ module Mongo
|
|
51
49
|
# The name of the database.
|
52
50
|
attr_reader :name
|
53
51
|
|
52
|
+
# The Mongo::Connection instance connecting to the MongoDB server.
|
54
53
|
attr_reader :connection
|
55
|
-
|
56
|
-
# Host to which we are currently connected.
|
57
|
-
attr_reader :host
|
58
|
-
# Port to which we are currently connected.
|
59
|
-
attr_reader :port
|
60
|
-
|
54
|
+
|
61
55
|
# An array of [host, port] pairs.
|
62
56
|
attr_reader :nodes
|
63
57
|
|
64
|
-
# The
|
65
|
-
attr_reader :socket
|
66
|
-
|
67
|
-
# The logger instance if :logger is passed to initialize
|
58
|
+
# The logger instance if :logger is passed to initialize.
|
68
59
|
attr_reader :logger
|
69
60
|
|
70
|
-
|
71
|
-
def auto_reconnect?; @auto_reconnect; end
|
72
|
-
|
73
|
-
# A primary key factory object (or +nil+). See the README.doc file or
|
74
|
-
# DB#new for details.
|
61
|
+
# The primary key factory object (or +nil+).
|
75
62
|
attr_reader :pk_factory
|
76
63
|
|
77
64
|
def pk_factory=(pk_factory)
|
@@ -91,8 +78,7 @@ module Mongo
|
|
91
78
|
# Options:
|
92
79
|
#
|
93
80
|
# :strict :: If true, collections must exist to be accessed and must
|
94
|
-
# not exist to be created. See #collection and
|
95
|
-
# #create_collection.
|
81
|
+
# not exist to be created. See #collection and #create_collection.
|
96
82
|
#
|
97
83
|
# :pk :: A primary key factory object that must respond to :create_pk,
|
98
84
|
# which should take a hash and return a hash which merges the
|
@@ -109,80 +95,22 @@ module Mongo
|
|
109
95
|
# see if the server is the master. If it is not, an error
|
110
96
|
# is thrown.
|
111
97
|
#
|
112
|
-
# :auto_reconnect :: If the connection gets closed (for example, we
|
113
|
-
# have a server pair and saw the "not master"
|
114
|
-
# error, which closes the connection), then
|
115
|
-
# automatically try to reconnect to the master or
|
116
|
-
# to the single server we have been given. Defaults
|
117
|
-
# to +false+.
|
118
98
|
# :logger :: Optional Logger instance to which driver usage information
|
119
99
|
# will be logged.
|
120
100
|
#
|
121
|
-
#
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
when Symbol, String
|
127
|
-
else
|
128
|
-
raise TypeError, "db_name must be a string or symbol"
|
129
|
-
end
|
130
|
-
|
131
|
-
[" ", ".", "$", "/", "\\"].each do |invalid_char|
|
132
|
-
if db_name.include? invalid_char
|
133
|
-
raise InvalidName, "database names cannot contain the character '#{invalid_char}'"
|
134
|
-
end
|
135
|
-
end
|
136
|
-
if db_name.empty?
|
137
|
-
raise InvalidName, "database name cannot be the empty string"
|
138
|
-
end
|
139
|
-
|
140
|
-
@connection = options[:connection]
|
141
|
-
|
142
|
-
@name, @nodes = db_name, nodes
|
143
|
-
@strict = options[:strict]
|
101
|
+
# :auto_reconnect :: DEPRECATED. See http://www.mongodb.org/display/DOCS/Replica+Pairs+in+Ruby
|
102
|
+
def initialize(db_name, connection, options={})
|
103
|
+
@name = validate_db_name(db_name)
|
104
|
+
@connection = connection
|
105
|
+
@strict = options[:strict]
|
144
106
|
@pk_factory = options[:pk]
|
145
|
-
@slave_ok = options[:slave_ok] && @nodes.length == 1 # only OK if one node
|
146
|
-
@auto_reconnect = options[:auto_reconnect]
|
147
|
-
@semaphore = Mutex.new
|
148
|
-
@socket = nil
|
149
|
-
@logger = options[:logger]
|
150
|
-
connect_to_master
|
151
|
-
end
|
152
|
-
|
153
|
-
def connect_to_master
|
154
|
-
close if @socket
|
155
|
-
@host = @port = nil
|
156
|
-
@nodes.detect { |hp|
|
157
|
-
@host, @port = *hp
|
158
|
-
begin
|
159
|
-
@socket = TCPSocket.new(@host, @port)
|
160
|
-
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
161
|
-
|
162
|
-
# Check for master. Can't call master? because it uses mutex,
|
163
|
-
# which may already be in use during this call.
|
164
|
-
semaphore_is_locked = @semaphore.locked?
|
165
|
-
@semaphore.unlock if semaphore_is_locked
|
166
|
-
is_master = master?
|
167
|
-
@semaphore.lock if semaphore_is_locked
|
168
|
-
|
169
|
-
if !@slave_ok && !is_master
|
170
|
-
raise ConfigurationError, "Trying to connect directly to slave; if this is what you want, specify :slave_ok => true."
|
171
|
-
end
|
172
|
-
@slave_ok || is_master
|
173
|
-
rescue SocketError, SystemCallError, IOError => ex
|
174
|
-
close if @socket
|
175
|
-
false
|
176
|
-
end
|
177
|
-
}
|
178
|
-
raise "error: failed to connect to any given host:port" unless @socket
|
179
107
|
end
|
180
108
|
|
181
109
|
# Returns true if +username+ has +password+ in
|
182
110
|
# +SYSTEM_USER_COLLECTION+. +name+ is username, +password+ is
|
183
111
|
# plaintext password.
|
184
112
|
def authenticate(username, password)
|
185
|
-
doc =
|
113
|
+
doc = command(:getnonce => 1)
|
186
114
|
raise "error retrieving nonce: #{doc}" unless ok?(doc)
|
187
115
|
nonce = doc['nonce']
|
188
116
|
|
@@ -191,12 +119,12 @@ module Mongo
|
|
191
119
|
auth['user'] = username
|
192
120
|
auth['nonce'] = nonce
|
193
121
|
auth['key'] = Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
|
194
|
-
ok?(
|
122
|
+
ok?(command(auth))
|
195
123
|
end
|
196
124
|
|
197
125
|
# Deauthorizes use for this database for this connection.
|
198
126
|
def logout
|
199
|
-
doc =
|
127
|
+
doc = command(:logout => 1)
|
200
128
|
raise "error logging out: #{doc.inspect}" unless ok?(doc)
|
201
129
|
end
|
202
130
|
|
@@ -250,7 +178,7 @@ module Mongo
|
|
250
178
|
# Create new collection
|
251
179
|
oh = OrderedHash.new
|
252
180
|
oh[:create] = name
|
253
|
-
doc =
|
181
|
+
doc = command(oh.merge(options || {}))
|
254
182
|
ok = doc['ok']
|
255
183
|
return Collection.new(self, name, @pk_factory) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
|
256
184
|
raise "Error creating collection: #{doc.inspect}"
|
@@ -274,20 +202,20 @@ module Mongo
|
|
274
202
|
def drop_collection(name)
|
275
203
|
return true unless collection_names.include?(name)
|
276
204
|
|
277
|
-
ok?(
|
205
|
+
ok?(command(:drop => name))
|
278
206
|
end
|
279
207
|
|
280
208
|
# Returns the error message from the most recently executed database
|
281
209
|
# operation for this connection, or +nil+ if there was no error.
|
282
210
|
def error
|
283
|
-
doc =
|
211
|
+
doc = command(:getlasterror => 1)
|
284
212
|
raise "error retrieving last error: #{doc}" unless ok?(doc)
|
285
213
|
doc['err']
|
286
214
|
end
|
287
215
|
|
288
216
|
# Get status information from the last operation on this connection.
|
289
217
|
def last_status
|
290
|
-
|
218
|
+
command(:getlasterror => 1)
|
291
219
|
end
|
292
220
|
|
293
221
|
# Returns +true+ if an error was caused by the most recently executed
|
@@ -301,7 +229,7 @@ module Mongo
|
|
301
229
|
# Only returns errors that have occured since the last call to
|
302
230
|
# DB#reset_error_history - returns +nil+ if there is no such error.
|
303
231
|
def previous_error
|
304
|
-
error =
|
232
|
+
error = command(:getpreverror => 1)
|
305
233
|
if error["err"]
|
306
234
|
error
|
307
235
|
else
|
@@ -314,52 +242,7 @@ module Mongo
|
|
314
242
|
# Calls to DB#previous_error will only return errors that have occurred
|
315
243
|
# since the most recent call to this method.
|
316
244
|
def reset_error_history
|
317
|
-
|
318
|
-
end
|
319
|
-
|
320
|
-
# Returns true if this database is a master (or is not paired with any
|
321
|
-
# other database), false if it is a slave.
|
322
|
-
def master?
|
323
|
-
doc = db_command(:ismaster => 1)
|
324
|
-
is_master = doc['ismaster']
|
325
|
-
ok?(doc) && is_master.kind_of?(Numeric) && is_master.to_i == 1
|
326
|
-
end
|
327
|
-
|
328
|
-
# Returns a string of the form "host:port" that points to the master
|
329
|
-
# database. Works even if this is the master database.
|
330
|
-
def master
|
331
|
-
doc = db_command(:ismaster => 1)
|
332
|
-
is_master = doc['ismaster']
|
333
|
-
raise "Error retrieving master database: #{doc.inspect}" unless ok?(doc) && is_master.kind_of?(Numeric)
|
334
|
-
case is_master.to_i
|
335
|
-
when 1
|
336
|
-
"#@host:#@port"
|
337
|
-
else
|
338
|
-
doc['remote']
|
339
|
-
end
|
340
|
-
end
|
341
|
-
|
342
|
-
# Close the connection to the database.
|
343
|
-
def close
|
344
|
-
if @socket
|
345
|
-
s = @socket
|
346
|
-
@socket = nil
|
347
|
-
s.close
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
def connected?
|
352
|
-
@socket != nil
|
353
|
-
end
|
354
|
-
|
355
|
-
def receive_full(length)
|
356
|
-
message = ""
|
357
|
-
while message.length < length do
|
358
|
-
chunk = @socket.recv(length - message.length)
|
359
|
-
raise "connection closed" unless chunk.length > 0
|
360
|
-
message += chunk
|
361
|
-
end
|
362
|
-
message
|
245
|
+
command(:reseterror => 1)
|
363
246
|
end
|
364
247
|
|
365
248
|
# Returns a Cursor over the query results.
|
@@ -388,7 +271,7 @@ module Mongo
|
|
388
271
|
oh = OrderedHash.new
|
389
272
|
oh[:$eval] = code
|
390
273
|
oh[:args] = args
|
391
|
-
doc =
|
274
|
+
doc = command(oh)
|
392
275
|
return doc['retval'] if ok?(doc)
|
393
276
|
raise OperationFailure, "eval failed: #{doc['errmsg']}"
|
394
277
|
end
|
@@ -399,7 +282,7 @@ module Mongo
|
|
399
282
|
oh = OrderedHash.new
|
400
283
|
oh[:renameCollection] = "#{@name}.#{from}"
|
401
284
|
oh[:to] = "#{@name}.#{to}"
|
402
|
-
doc =
|
285
|
+
doc = command(oh, true)
|
403
286
|
raise "Error renaming collection: #{doc.inspect}" unless ok?(doc)
|
404
287
|
end
|
405
288
|
|
@@ -409,7 +292,7 @@ module Mongo
|
|
409
292
|
oh = OrderedHash.new
|
410
293
|
oh[:deleteIndexes] = collection_name
|
411
294
|
oh[:index] = name
|
412
|
-
doc =
|
295
|
+
doc = command(oh)
|
413
296
|
raise "Error with drop_index command: #{doc.inspect}" unless ok?(doc)
|
414
297
|
end
|
415
298
|
|
@@ -422,7 +305,7 @@ module Mongo
|
|
422
305
|
sel = {:ns => full_collection_name(collection_name)}
|
423
306
|
info = {}
|
424
307
|
Cursor.new(Collection.new(self, SYSTEM_INDEX_COLLECTION), :selector => sel).each { |index|
|
425
|
-
info[index['name']] = index['key'].map
|
308
|
+
info[index['name']] = index['key'].map {|k| k}
|
426
309
|
}
|
427
310
|
info
|
428
311
|
end
|
@@ -436,53 +319,7 @@ module Mongo
|
|
436
319
|
def create_index(collection_name, field_or_spec, unique=false)
|
437
320
|
self.collection(collection_name).create_index(field_or_spec, unique)
|
438
321
|
end
|
439
|
-
|
440
|
-
# Sends a message to MongoDB.
|
441
|
-
#
|
442
|
-
# Takes a MongoDB opcode, +operation+, a message of class ByteBuffer,
|
443
|
-
# +message+, and an optional formatted +log_message+.
|
444
|
-
# Sends the message to the databse, adding the necessary headers.
|
445
|
-
def send_message_with_operation(operation, message, log_message=nil)
|
446
|
-
message_with_headers = add_message_headers(operation, message).to_s
|
447
|
-
@logger.debug(" MONGODB #{log_message || message}") if @logger
|
448
|
-
@semaphore.synchronize do
|
449
|
-
send_message_on_socket(message_with_headers)
|
450
|
-
end
|
451
|
-
end
|
452
|
-
|
453
|
-
def send_message_with_operation_raw(operation, message, log_message=nil)
|
454
|
-
message_with_headers = add_message_headers_raw(operation, message)
|
455
|
-
@logger.debug(" MONGODB #{log_message || message}") if @logger
|
456
|
-
@semaphore.synchronize do
|
457
|
-
send_message_on_socket(message_with_headers)
|
458
|
-
end
|
459
|
-
end
|
460
|
-
|
461
|
-
# Sends a message to the database, waits for a response, and raises
|
462
|
-
# and exception if the operation has failed.
|
463
|
-
def send_message_with_safe_check(operation, message, log_message=nil)
|
464
|
-
message_with_headers = add_message_headers(operation, message)
|
465
|
-
message_with_check = last_error_message
|
466
|
-
@logger.debug(" MONGODB #{log_message || message}") if @logger
|
467
|
-
@semaphore.synchronize do
|
468
|
-
send_message_on_socket(message_with_headers.append!(message_with_check).to_s)
|
469
|
-
docs, num_received, cursor_id = receive
|
470
|
-
if num_received == 1 && error = docs[0]['err']
|
471
|
-
raise Mongo::OperationFailure, error
|
472
|
-
end
|
473
|
-
end
|
474
|
-
end
|
475
|
-
|
476
|
-
# Send a message to the database and waits for the response.
|
477
|
-
def receive_message_with_operation(operation, message, log_message=nil)
|
478
|
-
message_with_headers = add_message_headers(operation, message).to_s
|
479
|
-
@logger.debug(" MONGODB #{log_message || message}") if @logger
|
480
|
-
@semaphore.synchronize do
|
481
|
-
send_message_on_socket(message_with_headers)
|
482
|
-
receive
|
483
|
-
end
|
484
|
-
end
|
485
|
-
|
322
|
+
|
486
323
|
# Return +true+ if +doc+ contains an 'ok' field with the value 1.
|
487
324
|
def ok?(doc)
|
488
325
|
ok = doc['ok']
|
@@ -492,14 +329,14 @@ module Mongo
|
|
492
329
|
# DB commands need to be ordered, so selector must be an OrderedHash
|
493
330
|
# (or a Hash with only one element). What DB commands really need is
|
494
331
|
# that the "command" key be first.
|
495
|
-
def
|
332
|
+
def command(selector, use_admin_db=false, sock=nil)
|
496
333
|
if !selector.kind_of?(OrderedHash)
|
497
334
|
if !selector.kind_of?(Hash) || selector.keys.length > 1
|
498
|
-
raise "
|
335
|
+
raise "command must be given an OrderedHash when there is more than one key"
|
499
336
|
end
|
500
337
|
end
|
501
338
|
|
502
|
-
cursor = Cursor.new(Collection.new(self, SYSTEM_COMMAND_COLLECTION), :admin => use_admin_db, :limit => -1, :selector => selector)
|
339
|
+
cursor = Cursor.new(Collection.new(self, SYSTEM_COMMAND_COLLECTION), :admin => use_admin_db, :limit => -1, :selector => selector, :socket => sock)
|
503
340
|
cursor.next_object
|
504
341
|
end
|
505
342
|
|
@@ -516,14 +353,14 @@ module Mongo
|
|
516
353
|
#
|
517
354
|
# Note: DB commands must start with the "command" key. For this reason,
|
518
355
|
# any selector containing more than one key must be an OrderedHash.
|
519
|
-
def command(selector, admin=false, check_response=false)
|
356
|
+
def command(selector, admin=false, check_response=false, sock=nil)
|
520
357
|
raise MongoArgumentError, "command must be given a selector" unless selector.is_a?(Hash) && !selector.empty?
|
521
358
|
if selector.class.eql?(Hash) && selector.keys.length > 1
|
522
359
|
raise MongoArgumentError, "DB#command requires an OrderedHash when hash contains multiple keys"
|
523
360
|
end
|
524
361
|
|
525
362
|
result = Cursor.new(system_command_collection, :admin => admin,
|
526
|
-
:limit => -1, :selector => selector).next_object
|
363
|
+
:limit => -1, :selector => selector, :socket => sock).next_object
|
527
364
|
|
528
365
|
if check_response && !ok?(result)
|
529
366
|
raise OperationFailure, "Database command '#{selector.keys.first}' failed."
|
@@ -532,147 +369,38 @@ module Mongo
|
|
532
369
|
end
|
533
370
|
end
|
534
371
|
|
372
|
+
# DEPRECATED: please use DB#command instead.
|
373
|
+
def db_command(*args)
|
374
|
+
warn "DB#db_command has been DEPRECATED. Please use DB#command instead."
|
375
|
+
command(args[0], args[1])
|
376
|
+
end
|
377
|
+
|
535
378
|
def full_collection_name(collection_name)
|
536
379
|
"#{@name}.#{collection_name}"
|
537
380
|
end
|
538
381
|
|
539
382
|
private
|
540
383
|
|
541
|
-
def
|
542
|
-
|
543
|
-
number_received, cursor_id = receive_response_header
|
544
|
-
read_documents(number_received, cursor_id)
|
545
|
-
end
|
546
|
-
|
547
|
-
def receive_header
|
548
|
-
header = ByteBuffer.new
|
549
|
-
header.put_array(receive_data_on_socket(16).unpack("C*"))
|
550
|
-
unless header.size == STANDARD_HEADER_SIZE
|
551
|
-
raise "Short read for DB response header: " +
|
552
|
-
"expected #{STANDARD_HEADER_SIZE} bytes, saw #{header.size}"
|
553
|
-
end
|
554
|
-
header.rewind
|
555
|
-
size = header.get_int
|
556
|
-
request_id = header.get_int
|
557
|
-
response_to = header.get_int
|
558
|
-
op = header.get_int
|
559
|
-
end
|
560
|
-
|
561
|
-
def receive_response_header
|
562
|
-
header_buf = ByteBuffer.new
|
563
|
-
header_buf.put_array(receive_data_on_socket(RESPONSE_HEADER_SIZE).unpack("C*"))
|
564
|
-
if header_buf.length != RESPONSE_HEADER_SIZE
|
565
|
-
raise "Short read for DB response header; " +
|
566
|
-
"expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}"
|
567
|
-
end
|
568
|
-
header_buf.rewind
|
569
|
-
result_flags = header_buf.get_int
|
570
|
-
cursor_id = header_buf.get_long
|
571
|
-
starting_from = header_buf.get_int
|
572
|
-
number_remaining = header_buf.get_int
|
573
|
-
[number_remaining, cursor_id]
|
384
|
+
def hash_password(username, plaintext)
|
385
|
+
Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
|
574
386
|
end
|
575
387
|
|
576
|
-
def
|
577
|
-
|
578
|
-
number_remaining = number_received
|
579
|
-
while number_remaining > 0 do
|
580
|
-
buf = ByteBuffer.new
|
581
|
-
buf.put_array(receive_data_on_socket(4).unpack("C*"))
|
582
|
-
buf.rewind
|
583
|
-
size = buf.get_int
|
584
|
-
buf.put_array(receive_data_on_socket(size - 4).unpack("C*"), 4)
|
585
|
-
number_remaining -= 1
|
586
|
-
buf.rewind
|
587
|
-
docs << BSON.new.deserialize(buf)
|
588
|
-
end
|
589
|
-
[docs, number_received, cursor_id]
|
388
|
+
def system_command_collection
|
389
|
+
Collection.new(self, SYSTEM_COMMAND_COLLECTION)
|
590
390
|
end
|
591
391
|
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
begin
|
596
|
-
@socket.print(packed_message)
|
597
|
-
@socket.flush
|
598
|
-
rescue => ex
|
599
|
-
close
|
600
|
-
raise ex
|
392
|
+
def validate_db_name(db_name)
|
393
|
+
unless [String, Symbol].include?(db_name.class)
|
394
|
+
raise TypeError, "db_name must be a string or symbol"
|
601
395
|
end
|
602
|
-
end
|
603
396
|
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
chunk = @socket.recv(length - message.length)
|
609
|
-
raise "connection closed" unless chunk.length > 0
|
610
|
-
message += chunk
|
397
|
+
[" ", ".", "$", "/", "\\"].each do |invalid_char|
|
398
|
+
if db_name.include? invalid_char
|
399
|
+
raise InvalidName, "database names cannot contain the character '#{invalid_char}'"
|
400
|
+
end
|
611
401
|
end
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
# Prepares a message for transmission to MongoDB by
|
616
|
-
# constructing a valid message header.
|
617
|
-
def add_message_headers(operation, message)
|
618
|
-
headers = ByteBuffer.new
|
619
|
-
|
620
|
-
# Message size.
|
621
|
-
headers.put_int(16 + message.size)
|
622
|
-
|
623
|
-
# Unique request id.
|
624
|
-
headers.put_int(get_request_id)
|
625
|
-
|
626
|
-
# Response id.
|
627
|
-
headers.put_int(0)
|
628
|
-
|
629
|
-
# Opcode.
|
630
|
-
headers.put_int(operation)
|
631
|
-
message.prepend!(headers)
|
632
|
-
end
|
633
|
-
|
634
|
-
# Increments and then returns the next available request id.
|
635
|
-
# Note: this method should be called from within a lock.
|
636
|
-
def get_request_id
|
637
|
-
@@current_request_id += 1
|
638
|
-
@@current_request_id
|
639
|
-
end
|
640
|
-
|
641
|
-
# Creates a getlasterror message.
|
642
|
-
def last_error_message
|
643
|
-
generate_last_error_message
|
644
|
-
end
|
645
|
-
|
646
|
-
def generate_last_error_message
|
647
|
-
message = ByteBuffer.new
|
648
|
-
message.put_int(0)
|
649
|
-
BSON.serialize_cstr(message, "#{@name}.$cmd")
|
650
|
-
message.put_int(0)
|
651
|
-
message.put_int(-1)
|
652
|
-
message.put_array(BSON_SERIALIZER.serialize({:getlasterror => 1}, false).unpack("C*"))
|
653
|
-
add_message_headers(Mongo::Constants::OP_QUERY, message)
|
654
|
-
end
|
655
|
-
|
656
|
-
def reset_error_message
|
657
|
-
@@reset_error_message ||= generate_reset_error_message
|
658
|
-
end
|
659
|
-
|
660
|
-
def generate_reset_error_message
|
661
|
-
message = ByteBuffer.new
|
662
|
-
message.put_int(0)
|
663
|
-
BSON.serialize_cstr(message, "#{@name}.$cmd")
|
664
|
-
message.put_int(0)
|
665
|
-
message.put_int(-1)
|
666
|
-
message.put_array(BSON_SERIALIZER.serialize({:reseterror => 1}, false).unpack("C*"))
|
667
|
-
add_message_headers(Mongo::Constants::OP_QUERY, message)
|
668
|
-
end
|
669
|
-
|
670
|
-
def hash_password(username, plaintext)
|
671
|
-
Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
|
672
|
-
end
|
673
|
-
|
674
|
-
def system_command_collection
|
675
|
-
Collection.new(self, SYSTEM_COMMAND_COLLECTION)
|
402
|
+
raise InvalidName, "database name cannot be the empty string" if db_name.empty?
|
403
|
+
db_name
|
676
404
|
end
|
677
405
|
end
|
678
406
|
end
|