mongo 0.17.1 → 0.18
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.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
|