mongo 0.15.1 → 0.16

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.
Files changed (63) hide show
  1. data/LICENSE.txt +202 -0
  2. data/README.rdoc +15 -2
  3. data/Rakefile +17 -3
  4. data/bin/autoreconnect.rb +26 -0
  5. data/bin/fail_if_no_c.rb +11 -0
  6. data/lib/mongo.rb +9 -2
  7. data/lib/mongo/admin.rb +1 -1
  8. data/lib/mongo/collection.rb +86 -25
  9. data/lib/mongo/connection.rb +11 -0
  10. data/lib/mongo/constants.rb +15 -0
  11. data/lib/mongo/cursor.rb +176 -36
  12. data/lib/mongo/db.rb +84 -97
  13. data/lib/mongo/errors.rb +7 -1
  14. data/lib/mongo/types/objectid.rb +5 -0
  15. data/lib/mongo/util/byte_buffer.rb +12 -0
  16. data/lib/mongo/util/server_version.rb +69 -0
  17. data/lib/mongo/{message.rb → util/support.rb} +7 -4
  18. data/mongo-ruby-driver.gemspec +9 -86
  19. data/test/test_admin.rb +2 -2
  20. data/test/test_bson.rb +14 -0
  21. data/test/test_byte_buffer.rb +14 -0
  22. data/test/test_chunk.rb +4 -4
  23. data/test/test_collection.rb +107 -17
  24. data/test/test_connection.rb +13 -4
  25. data/test/test_cursor.rb +17 -14
  26. data/test/test_db.rb +7 -7
  27. data/test/test_db_api.rb +31 -19
  28. data/test/test_db_connection.rb +1 -1
  29. data/test/test_grid_store.rb +8 -8
  30. data/test/test_helper.rb +25 -0
  31. data/test/test_objectid.rb +11 -1
  32. data/test/test_slave_connection.rb +37 -0
  33. data/test/test_threading.rb +1 -1
  34. data/test/unit/cursor_test.rb +122 -0
  35. metadata +26 -46
  36. data/bin/mongo_console +0 -21
  37. data/bin/run_test_script +0 -19
  38. data/bin/standard_benchmark +0 -109
  39. data/lib/mongo/message/get_more_message.rb +0 -32
  40. data/lib/mongo/message/insert_message.rb +0 -37
  41. data/lib/mongo/message/kill_cursors_message.rb +0 -31
  42. data/lib/mongo/message/message.rb +0 -80
  43. data/lib/mongo/message/message_header.rb +0 -45
  44. data/lib/mongo/message/msg_message.rb +0 -29
  45. data/lib/mongo/message/opcodes.rb +0 -27
  46. data/lib/mongo/message/query_message.rb +0 -69
  47. data/lib/mongo/message/remove_message.rb +0 -37
  48. data/lib/mongo/message/update_message.rb +0 -38
  49. data/lib/mongo/query.rb +0 -118
  50. data/test/mongo-qa/admin +0 -26
  51. data/test/mongo-qa/capped +0 -22
  52. data/test/mongo-qa/count1 +0 -18
  53. data/test/mongo-qa/dbs +0 -22
  54. data/test/mongo-qa/find +0 -10
  55. data/test/mongo-qa/find1 +0 -15
  56. data/test/mongo-qa/gridfs_in +0 -16
  57. data/test/mongo-qa/gridfs_out +0 -17
  58. data/test/mongo-qa/indices +0 -49
  59. data/test/mongo-qa/remove +0 -25
  60. data/test/mongo-qa/stress1 +0 -35
  61. data/test/mongo-qa/test1 +0 -11
  62. data/test/mongo-qa/update +0 -18
  63. data/test/test_message.rb +0 -35
@@ -120,6 +120,17 @@ module Mongo
120
120
  single_db_command(name, :dropDatabase => 1)
121
121
  end
122
122
 
123
+ # Return the build information for the current connection.
124
+ def server_info
125
+ db("admin").db_command(:buildinfo => 1)
126
+ end
127
+
128
+ # Returns the build version of the current server, using
129
+ # a ServerVersion object for comparability.
130
+ def server_version
131
+ ServerVersion.new(server_info["version"])
132
+ end
133
+
123
134
  protected
124
135
 
125
136
  # Turns an array containing a host name string and a
@@ -0,0 +1,15 @@
1
+ module Mongo
2
+ module Constants
3
+ OP_REPLY = 1
4
+ OP_MSG = 1000
5
+ OP_UPDATE = 2001
6
+ OP_INSERT = 2002
7
+ OP_QUERY = 2004
8
+ OP_GET_MORE = 2005
9
+ OP_DELETE = 2006
10
+ OP_KILL_CURSORS = 2007
11
+
12
+ OP_QUERY_SLAVE_OK = 4
13
+ OP_QUERY_NO_CURSOR_TIMEOUT = 16
14
+ end
15
+ end
@@ -12,7 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require 'mongo/message'
16
15
  require 'mongo/util/byte_buffer'
17
16
  require 'mongo/util/bson'
18
17
 
@@ -20,18 +19,35 @@ module Mongo
20
19
 
21
20
  # A cursor over query results. Returned objects are hashes.
22
21
  class Cursor
22
+ include Mongo::Conversions
23
23
 
24
24
  include Enumerable
25
25
 
26
26
  RESPONSE_HEADER_SIZE = 20
27
27
 
28
- attr_reader :db, :collection, :query
28
+ attr_reader :collection, :selector, :admin, :fields,
29
+ :order, :hint, :snapshot, :timeout,
30
+ :full_collection_name
29
31
 
30
32
  # Create a new cursor.
31
33
  #
32
34
  # Should not be called directly by application developers.
33
- def initialize(db, collection, query, admin=false)
34
- @db, @collection, @query, @admin = db, collection, query, admin
35
+ def initialize(collection, options={})
36
+ @db = collection.db
37
+ @collection = collection
38
+
39
+ @selector = convert_selector_for_query(options[:selector])
40
+ @fields = convert_fields_for_query(options[:fields])
41
+ @admin = options[:admin] || false
42
+ @skip = options[:skip] || 0
43
+ @limit = options[:limit] || 0
44
+ @order = options[:order]
45
+ @hint = options[:hint]
46
+ @snapshot = options[:snapshot]
47
+ @timeout = options[:timeout] || false
48
+ @explain = options[:explain]
49
+
50
+ @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
35
51
  @cache = []
36
52
  @closed = false
37
53
  @query_run = false
@@ -64,9 +80,9 @@ module Mongo
64
80
  # not take limit and skip into account. Raises OperationFailure on a
65
81
  # database error.
66
82
  def count
67
- command = OrderedHash["count", @collection.name,
68
- "query", @query.selector,
69
- "fields", @query.fields()]
83
+ command = OrderedHash["count", @collection.name,
84
+ "query", @selector,
85
+ "fields", @fields]
70
86
  response = @db.db_command(command)
71
87
  return response['n'].to_i if response['ok'] == 1
72
88
  return 0 if response['errmsg'] == "ns missing"
@@ -93,35 +109,39 @@ module Mongo
93
109
  order = key_or_list
94
110
  end
95
111
 
96
- @query.order_by = order
112
+ @order = order
97
113
  self
98
114
  end
99
115
 
100
116
  # Limits the number of results to be returned by this cursor.
117
+ # Returns the current number_to_return if no parameter is given.
101
118
  #
102
119
  # Raises InvalidOperation if this cursor has already been used.
103
120
  #
104
121
  # This method overrides any limit specified in the Collection#find method,
105
122
  # and only the last limit applied has an effect.
106
- def limit(number_to_return)
123
+ def limit(number_to_return=nil)
124
+ return @limit unless number_to_return
107
125
  check_modifiable
108
126
  raise ArgumentError, "limit requires an integer" unless number_to_return.is_a? Integer
109
127
 
110
- @query.number_to_return = number_to_return
128
+ @limit = number_to_return
111
129
  self
112
130
  end
113
131
 
114
132
  # Skips the first +number_to_skip+ results of this cursor.
115
- #
133
+ # Returns the current number_to_skip if no parameter is given.
134
+ #
116
135
  # Raises InvalidOperation if this cursor has already been used.
117
136
  #
118
137
  # This method overrides any skip specified in the Collection#find method,
119
138
  # and only the last skip applied has an effect.
120
- def skip(number_to_skip)
139
+ def skip(number_to_skip=nil)
140
+ return @skip unless number_to_skip
121
141
  check_modifiable
122
142
  raise ArgumentError, "skip requires an integer" unless number_to_skip.is_a? Integer
123
143
 
124
- @query.number_to_skip = number_to_skip
144
+ @skip = number_to_skip
125
145
  self
126
146
  end
127
147
 
@@ -131,7 +151,7 @@ module Mongo
131
151
  # Iterating over an entire cursor will close it.
132
152
  def each
133
153
  num_returned = 0
134
- while more? && (@query.number_to_return <= 0 || num_returned < @query.number_to_return)
154
+ while more? && (@limit <= 0 || num_returned < @limit)
135
155
  yield next_object()
136
156
  num_returned += 1
137
157
  end
@@ -148,7 +168,7 @@ module Mongo
148
168
  raise InvalidOperation, "can't call Cursor#to_a on a used cursor" if @query_run
149
169
  rows = []
150
170
  num_returned = 0
151
- while more? && (@query.number_to_return <= 0 || num_returned < @query.number_to_return)
171
+ while more? && (@limit <= 0 || num_returned < @limit)
152
172
  rows << next_object()
153
173
  num_returned += 1
154
174
  end
@@ -157,39 +177,96 @@ module Mongo
157
177
 
158
178
  # Returns an explain plan record for this cursor.
159
179
  def explain
160
- limit = @query.number_to_return
161
- @query.explain = true
162
- @query.number_to_return = -limit.abs
163
-
164
- c = Cursor.new(@db, @collection, @query)
180
+ c = Cursor.new(@collection, query_options_hash.merge(:limit => -@limit.abs, :explain => true))
165
181
  explanation = c.next_object
166
182
  c.close
167
183
 
168
- @query.explain = false
169
- @query.number_to_return = limit
170
184
  explanation
171
185
  end
172
186
 
173
187
  # Close the cursor.
174
188
  #
175
- # Note: if a cursor is read until exhausted (read until OP_QUERY or
176
- # OP_GETMORE returns zero for the cursor id), there is no need to
189
+ # Note: if a cursor is read until exhausted (read until Mongo::Constants::OP_QUERY or
190
+ # Mongo::Constants::OP_GETMORE returns zero for the cursor id), there is no need to
177
191
  # close it by calling this method.
178
192
  #
179
193
  # Collection#find takes an optional block argument which can be used to
180
194
  # ensure that your cursors get closed. See the documentation for
181
195
  # Collection#find for details.
182
196
  def close
183
- @db.send_to_db(KillCursorsMessage.new(@cursor_id)) if @cursor_id
197
+ if @cursor_id
198
+ message = ByteBuffer.new
199
+ message.put_int(0)
200
+ message.put_int(1)
201
+ message.put_long(@cursor_id)
202
+ @db.send_message_with_operation(Mongo::Constants::OP_KILL_CURSORS, message)
203
+ end
184
204
  @cursor_id = 0
185
- @closed = true
205
+ @closed = true
186
206
  end
187
207
 
188
208
  # Returns true if this cursor is closed, false otherwise.
189
209
  def closed?; @closed; end
190
210
 
211
+ # Returns an integer indicating which query options have been selected.
212
+ # See http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
213
+ def query_opts
214
+ timeout = @timeout ? 0 : Mongo::Constants::OP_QUERY_NO_CURSOR_TIMEOUT
215
+ slave_ok = @db.slave_ok? ? Mongo::Constants::OP_QUERY_SLAVE_OK : 0
216
+ slave_ok + timeout
217
+ end
218
+
219
+ # Returns the query options set on this Cursor.
220
+ def query_options_hash
221
+ { :selector => @selector,
222
+ :fields => @fields,
223
+ :admin => @admin,
224
+ :skip => @skip_num,
225
+ :limit => @limit_num,
226
+ :order => @order,
227
+ :hint => @hint,
228
+ :snapshot => @snapshot,
229
+ :timeout => @timeout }
230
+ end
231
+
191
232
  private
192
233
 
234
+ # Converts the +:fields+ parameter from a single field name or an array
235
+ # of fields names to a hash, with the field names for keys and '1' for each
236
+ # value.
237
+ def convert_fields_for_query(fields)
238
+ case fields
239
+ when String, Symbol
240
+ {fields => 1}
241
+ when Array
242
+ return nil if fields.length.zero?
243
+ returning({}) do |hash|
244
+ fields.each { |field| hash[field] = 1 }
245
+ end
246
+ end
247
+ end
248
+
249
+ # Set query selector hash. If the selector is a Code or String object,
250
+ # the selector will be used in a $where clause.
251
+ # See http://www.mongodb.org/display/DOCS/Server-side+Code+Execution
252
+ def convert_selector_for_query(selector)
253
+ case selector
254
+ when Hash
255
+ selector
256
+ when nil
257
+ {}
258
+ when String
259
+ {"$where" => Code.new(selector)}
260
+ when Code
261
+ {"$where" => selector}
262
+ end
263
+ end
264
+
265
+ # Returns true if the query contains order, explain, hint, or snapshot.
266
+ def query_contains_special_fields?
267
+ @order || @explain || @hint || @snapshot
268
+ end
269
+
193
270
  def read_all
194
271
  read_message_header
195
272
  read_response_header
@@ -203,7 +280,16 @@ module Mongo
203
280
  end
204
281
 
205
282
  def read_message_header
206
- MessageHeader.new.read_header(@db)
283
+ message = ByteBuffer.new
284
+ message.put_array(@db.receive_full(16).unpack("C*"))
285
+ unless message.size == 16 #HEADER_SIZE
286
+ raise "Short read for DB response header: expected #{16} bytes, saw #{message.size}"
287
+ end
288
+ message.rewind
289
+ size = message.get_int
290
+ request_id = message.get_int
291
+ response_to = message.get_int
292
+ op = message.get_int
207
293
  end
208
294
 
209
295
  def read_response_header
@@ -220,9 +306,6 @@ module Mongo
220
306
  else
221
307
  @n_received = @n_remaining
222
308
  end
223
- if @query.number_to_return > 0 and @n_received >= @query.number_to_return
224
- close()
225
- end
226
309
  end
227
310
 
228
311
  def num_remaining
@@ -231,7 +314,7 @@ module Mongo
231
314
  end
232
315
 
233
316
  # Internal method, not for general use. Return +true+ if there are
234
- # more records to retrieve. We do not check @query.number_to_return;
317
+ # more records to retrieve. This methods does not check @limit;
235
318
  # #each is responsible for doing that.
236
319
  def more?
237
320
  num_remaining > 0
@@ -246,13 +329,25 @@ module Mongo
246
329
  end
247
330
 
248
331
  def refill_via_get_more
249
- if send_query_if_needed or @cursor_id == 0
250
- return
251
- end
332
+ return if send_query_if_needed || @cursor_id.zero?
252
333
  @db._synchronize {
253
- @db.send_to_db(GetMoreMessage.new(@admin ? 'admin' : @db.name, @collection.name, @cursor_id))
334
+ message = ByteBuffer.new
335
+ # Reserved.
336
+ message.put_int(0)
337
+
338
+ # DB name.
339
+ db_name = @admin ? 'admin' : @db.name
340
+ BSON.serialize_cstr(message, "#{db_name}.#{@collection.name}")
341
+
342
+ # Number of results to return; db decides for now.
343
+ message.put_int(0)
344
+
345
+ # Cursor id.
346
+ message.put_long(@cursor_id)
347
+ @db.send_message_with_operation_without_synchronize(Mongo::Constants::OP_GET_MORE, message)
254
348
  read_all
255
349
  }
350
+ close_cursor_if_query_complete
256
351
  end
257
352
 
258
353
  def object_from_stream
@@ -271,19 +366,64 @@ module Mongo
271
366
  if @query_run
272
367
  false
273
368
  else
369
+ message = construct_query_message(@query)
274
370
  @db._synchronize {
275
- @db.send_query_message(QueryMessage.new(@admin ? 'admin' : @db.name, @collection.name, @query))
371
+ @db.send_message_with_operation_without_synchronize(Mongo::Constants::OP_QUERY, message)
276
372
  @query_run = true
277
373
  read_all
278
374
  }
375
+ close_cursor_if_query_complete
279
376
  true
280
377
  end
281
378
  end
282
379
 
380
+ def construct_query_message(query)
381
+ message = ByteBuffer.new
382
+ message.put_int(query_opts)
383
+ db_name = @admin ? 'admin' : @db.name
384
+ BSON.serialize_cstr(message, "#{db_name}.#{@collection.name}")
385
+ message.put_int(@skip)
386
+ message.put_int(@limit)
387
+ selector = @selector
388
+ if query_contains_special_fields?
389
+ selector = selector_with_special_query_fields
390
+ end
391
+ message.put_array(BSON.new.serialize(selector).to_a)
392
+ message.put_array(BSON.new.serialize(@fields).to_a) if @fields
393
+ message
394
+ end
395
+
396
+ def selector_with_special_query_fields
397
+ sel = OrderedHash.new
398
+ sel['query'] = @selector
399
+ sel['orderby'] = formatted_order_clause if @order
400
+ sel['$hint'] = @hint if @hint && @hint.length > 0
401
+ sel['$explain'] = true if @explain
402
+ sel['$snapshot'] = true if @snapshot
403
+ sel
404
+ end
405
+
406
+ def formatted_order_clause
407
+ case @order
408
+ when String then string_as_sort_parameters(@order)
409
+ when Symbol then symbol_as_sort_parameters(@order)
410
+ when Array then array_as_sort_parameters(@order)
411
+ when Hash # Should be an ordered hash, but this message doesn't care
412
+ warn_if_deprecated(@order)
413
+ @order
414
+ else
415
+ raise InvalidSortValueError, "Illegal order_by, '#{@order.class.name}'; must be String, Array, Hash, or OrderedHash"
416
+ end
417
+ end
418
+
283
419
  def to_s
284
420
  "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)"
285
421
  end
286
422
 
423
+ def close_cursor_if_query_complete
424
+ close if @limit > 0 && @n_received >= @limit
425
+ end
426
+
287
427
  def check_modifiable
288
428
  if @query_run || @closed
289
429
  raise InvalidOperation, "Cannot modify the query once it has been run or closed."
@@ -16,10 +16,8 @@
16
16
 
17
17
  require 'socket'
18
18
  require 'digest/md5'
19
- require 'mutex_m'
19
+ require 'thread'
20
20
  require 'mongo/collection'
21
- require 'mongo/message'
22
- require 'mongo/query'
23
21
  require 'mongo/util/ordered_hash.rb'
24
22
  require 'mongo/admin'
25
23
 
@@ -34,6 +32,9 @@ module Mongo
34
32
  SYSTEM_USER_COLLECTION = "system.users"
35
33
  SYSTEM_COMMAND_COLLECTION = "$cmd"
36
34
 
35
+ # Counter for generating unique request ids.
36
+ @@current_request_id = 0
37
+
37
38
  # Strict mode enforces collection existence checks. When +true+,
38
39
  # asking for a collection that does not exist or trying to create a
39
40
  # collection that already exists raises an error.
@@ -137,8 +138,7 @@ module Mongo
137
138
  @pk_factory = options[:pk]
138
139
  @slave_ok = options[:slave_ok] && @nodes.length == 1 # only OK if one node
139
140
  @auto_reconnect = options[:auto_reconnect]
140
- @semaphore = Object.new
141
- @semaphore.extend Mutex_m
141
+ @semaphore = Mutex.new
142
142
  @socket = nil
143
143
  @logger = options[:logger]
144
144
  connect_to_master
@@ -160,6 +160,9 @@ module Mongo
160
160
  is_master = master?
161
161
  @semaphore.lock if semaphore_is_locked
162
162
 
163
+ if !@slave_ok && !is_master
164
+ raise ConfigurationError, "Trying to connect directly to slave; if this is what you want, specify :slave_ok => true."
165
+ end
163
166
  @slave_ok || is_master
164
167
  rescue SocketError, SystemCallError, IOError => ex
165
168
  close if @socket
@@ -211,8 +214,8 @@ module Mongo
211
214
  # specified, an array of length 1 is returned.
212
215
  def collections_info(coll_name=nil)
213
216
  selector = {}
214
- selector[:name] = full_coll_name(coll_name) if coll_name
215
- query(Collection.new(self, SYSTEM_NAMESPACE_COLLECTION), Query.new(selector))
217
+ selector[:name] = full_collection_name(coll_name) if coll_name
218
+ Cursor.new(Collection.new(self, SYSTEM_NAMESPACE_COLLECTION), :selector => selector)
216
219
  end
217
220
 
218
221
  # Create a collection. If +strict+ is false, will return existing or
@@ -243,7 +246,7 @@ module Mongo
243
246
  oh[:create] = name
244
247
  doc = db_command(oh.merge(options || {}))
245
248
  ok = doc['ok']
246
- return Collection.new(self, name) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
249
+ return Collection.new(self, name, @pk_factory) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
247
250
  raise "Error creating collection: #{doc.inspect}"
248
251
  end
249
252
 
@@ -255,7 +258,7 @@ module Mongo
255
258
  # new collection. If +strict+ is true, will raise an error if
256
259
  # collection +name+ does not already exists.
257
260
  def collection(name)
258
- return Collection.new(self, name) if !strict? || collection_names.include?(name)
261
+ return Collection.new(self, name, @pk_factory) if !strict? || collection_names.include?(name)
259
262
  raise "Collection #{name} doesn't exist. Currently in strict mode."
260
263
  end
261
264
  alias_method :[], :collection
@@ -353,11 +356,6 @@ module Mongo
353
356
  message
354
357
  end
355
358
 
356
- # Send a MsgMessage to the database.
357
- def send_message(msg)
358
- send_to_db(MsgMessage.new(msg))
359
- end
360
-
361
359
  # Returns a Cursor over the query results.
362
360
  #
363
361
  # Note that the query gets sent lazily; the cursor calls
@@ -367,38 +365,6 @@ module Mongo
367
365
  Cursor.new(self, collection, query, admin)
368
366
  end
369
367
 
370
- # Used by a Cursor to lazily send the query to the database.
371
- def send_query_message(query_message)
372
- send_to_db(query_message)
373
- end
374
-
375
- # Remove the records that match +selector+ from +collection_name+.
376
- # Normally called by Collection#remove or Collection#clear.
377
- def remove_from_db(collection_name, selector)
378
- _synchronize {
379
- send_to_db(RemoveMessage.new(@name, collection_name, selector))
380
- }
381
- end
382
-
383
- # Update records in +collection_name+ that match +selector+ by
384
- # applying +obj+ as an update. Normally called by Collection#replace.
385
- def replace_in_db(collection_name, selector, obj)
386
- _synchronize {
387
- send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, false))
388
- }
389
- end
390
-
391
- # Update records in +collection_name+ that match +selector+ by
392
- # applying +obj+ as an update. If no match, inserts (???). Normally
393
- # called by Collection#repsert.
394
- def repsert_in_db(collection_name, selector, obj)
395
- _synchronize {
396
- obj = @pk_factory.create_pk(obj) if @pk_factory
397
- send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, true))
398
- obj
399
- }
400
- end
401
-
402
368
  # Dereference a DBRef, getting the document it points to.
403
369
  def dereference(dbref)
404
370
  collection(dbref.namespace).find_one("_id" => dbref.object_id)
@@ -447,9 +413,9 @@ module Mongo
447
413
  # the values are lists of [key, direction] pairs specifying the index
448
414
  # (as passed to Collection#create_index).
449
415
  def index_information(collection_name)
450
- sel = {:ns => full_coll_name(collection_name)}
416
+ sel = {:ns => full_collection_name(collection_name)}
451
417
  info = {}
452
- query(Collection.new(self, SYSTEM_INDEX_COLLECTION), Query.new(sel)).each { |index|
418
+ Cursor.new(Collection.new(self, SYSTEM_INDEX_COLLECTION), :selector => sel).each { |index|
453
419
  info[index['name']] = index['key'].to_a
454
420
  }
455
421
  info
@@ -462,42 +428,7 @@ module Mongo
462
428
  # by Collection#create_index. If +unique+ is true the index will
463
429
  # enforce a uniqueness constraint.
464
430
  def create_index(collection_name, field_or_spec, unique=false)
465
- field_h = OrderedHash.new
466
- if field_or_spec.is_a?(String) || field_or_spec.is_a?(Symbol)
467
- field_h[field_or_spec.to_s] = 1
468
- else
469
- field_or_spec.each { |f| field_h[f[0].to_s] = f[1] }
470
- end
471
- name = gen_index_name(field_h)
472
- sel = {
473
- :name => name,
474
- :ns => full_coll_name(collection_name),
475
- :key => field_h,
476
- :unique => unique
477
- }
478
- _synchronize {
479
- send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, false, sel))
480
- }
481
- name
482
- end
483
-
484
- # Insert +objects+ into +collection_name+. Normally called by
485
- # Collection#insert. Returns a new array containing the _ids
486
- # of the inserted documents.
487
- def insert_into_db(collection_name, objects)
488
- _synchronize {
489
- if @pk_factory
490
- objects.collect! { |o|
491
- @pk_factory.create_pk(o)
492
- }
493
- else
494
- objects = objects.collect do |o|
495
- o[:_id] || o['_id'] ? o : o.merge!(:_id => ObjectID.new)
496
- end
497
- end
498
- send_to_db(InsertMessage.new(@name, collection_name, true, *objects))
499
- objects.collect { |o| o[:_id] || o['_id'] }
500
- }
431
+ self.collection(collection_name).create_index(field_or_spec, unique)
501
432
  end
502
433
 
503
434
  def send_to_db(message)
@@ -512,8 +443,43 @@ module Mongo
512
443
  end
513
444
  end
514
445
 
515
- def full_coll_name(collection_name)
516
- "#{@name}.#{collection_name}"
446
+ # Sends a message to MongoDB.
447
+ #
448
+ # Takes a MongoDB opcode, +operation+, and a message of class ByteBuffer,
449
+ # +message+, and sends the message to the databse, adding the necessary headers.
450
+ def send_message_with_operation(operation, message)
451
+ @semaphore.synchronize do
452
+ connect_to_master if !connected? && @auto_reconnect
453
+ begin
454
+ message_with_headers = add_message_headers(operation, message)
455
+ @logger.debug(" MONGODB #{message}") if @logger
456
+ @socket.print(message_with_headers.to_s)
457
+ @socket.flush
458
+ rescue => ex
459
+ close
460
+ raise ex
461
+ end
462
+ end
463
+ end
464
+
465
+ def send_message_with_operation_without_synchronize(operation, message)
466
+ connect_to_master if !connected? && @auto_reconnect
467
+ begin
468
+ message_with_headers = add_message_headers(operation, message)
469
+ @logger.debug(" MONGODB #{operation} #{message}") if @logger
470
+ @socket.print(message_with_headers.to_s)
471
+ @socket.flush
472
+ rescue => ex
473
+ close
474
+ raise ex
475
+ end
476
+ end
477
+
478
+ def receive_message_with_operation(operation, message)
479
+ @semaphore.synchronize do
480
+
481
+
482
+ end
517
483
  end
518
484
 
519
485
  # Return +true+ if +doc+ contains an 'ok' field with the value 1.
@@ -532,27 +498,48 @@ module Mongo
532
498
  end
533
499
  end
534
500
 
535
- q = Query.new(selector)
536
- q.number_to_return = -1
537
- query(Collection.new(self, SYSTEM_COMMAND_COLLECTION), q, use_admin_db).next_object
501
+ cursor = Cursor.new(Collection.new(self, SYSTEM_COMMAND_COLLECTION), :admin => use_admin_db, :limit => -1, :selector => selector)
502
+ cursor.next_object
538
503
  end
539
504
 
540
505
  def _synchronize &block
541
506
  @semaphore.synchronize &block
542
507
  end
543
508
 
509
+ def full_collection_name(collection_name)
510
+ "#{@name}.#{collection_name}"
511
+ end
512
+
544
513
  private
545
514
 
546
- def hash_password(username, plaintext)
547
- Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
515
+ # Prepares a message for transmission to MongoDB by
516
+ # constructing a valid message header.
517
+ def add_message_headers(operation, message)
518
+ headers = ByteBuffer.new
519
+
520
+ # Message size.
521
+ headers.put_int(16 + message.size)
522
+
523
+ # Unique request id.
524
+ headers.put_int(get_request_id)
525
+
526
+ # Response id.
527
+ headers.put_int(0)
528
+
529
+ # Opcode.
530
+ headers.put_int(operation)
531
+ message.prepend!(headers)
548
532
  end
549
533
 
550
- def gen_index_name(spec)
551
- temp = []
552
- spec.each_pair { |field, direction|
553
- temp = temp.push("#{field}_#{direction}")
554
- }
555
- return temp.join("_")
534
+ # Increments and then returns the next available request id.
535
+ # Note: this method should be called from within a lock.
536
+ def get_request_id
537
+ @@current_request_id += 1
538
+ @@current_request_id
539
+ end
540
+
541
+ def hash_password(username, plaintext)
542
+ Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
556
543
  end
557
544
  end
558
545
  end