mongo 0.15.1 → 0.16

Sign up to get free protection for your applications and to get access to all the features.
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