mongo 1.0 → 1.1.5

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 (95) hide show
  1. data/LICENSE.txt +1 -13
  2. data/{README.rdoc → README.md} +129 -149
  3. data/Rakefile +94 -58
  4. data/bin/mongo_console +21 -0
  5. data/docs/1.0_UPGRADE.md +21 -0
  6. data/docs/CREDITS.md +123 -0
  7. data/docs/FAQ.md +112 -0
  8. data/docs/GridFS.md +158 -0
  9. data/docs/HISTORY.md +185 -0
  10. data/docs/REPLICA_SETS.md +75 -0
  11. data/docs/TUTORIAL.md +247 -0
  12. data/docs/WRITE_CONCERN.md +28 -0
  13. data/lib/mongo/collection.rb +225 -105
  14. data/lib/mongo/connection.rb +374 -315
  15. data/lib/mongo/cursor.rb +122 -77
  16. data/lib/mongo/db.rb +109 -85
  17. data/lib/mongo/exceptions.rb +6 -0
  18. data/lib/mongo/gridfs/grid.rb +19 -11
  19. data/lib/mongo/gridfs/grid_ext.rb +36 -9
  20. data/lib/mongo/gridfs/grid_file_system.rb +15 -9
  21. data/lib/mongo/gridfs/grid_io.rb +49 -16
  22. data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
  23. data/lib/mongo/repl_set_connection.rb +290 -0
  24. data/lib/mongo/util/conversions.rb +3 -1
  25. data/lib/mongo/util/core_ext.rb +17 -4
  26. data/lib/mongo/util/pool.rb +125 -0
  27. data/lib/mongo/util/server_version.rb +2 -0
  28. data/lib/mongo/util/support.rb +12 -0
  29. data/lib/mongo/util/uri_parser.rb +71 -0
  30. data/lib/mongo.rb +23 -7
  31. data/{mongo-ruby-driver.gemspec → mongo.gemspec} +9 -7
  32. data/test/auxillary/1.4_features.rb +2 -2
  33. data/test/auxillary/authentication_test.rb +1 -1
  34. data/test/auxillary/autoreconnect_test.rb +1 -1
  35. data/test/{slave_connection_test.rb → auxillary/slave_connection_test.rb} +6 -6
  36. data/test/bson/binary_test.rb +15 -0
  37. data/test/bson/bson_test.rb +537 -0
  38. data/test/bson/byte_buffer_test.rb +190 -0
  39. data/test/bson/hash_with_indifferent_access_test.rb +38 -0
  40. data/test/bson/json_test.rb +17 -0
  41. data/test/bson/object_id_test.rb +141 -0
  42. data/test/bson/ordered_hash_test.rb +197 -0
  43. data/test/collection_test.rb +195 -15
  44. data/test/connection_test.rb +93 -56
  45. data/test/conversions_test.rb +1 -1
  46. data/test/cursor_fail_test.rb +75 -0
  47. data/test/cursor_message_test.rb +43 -0
  48. data/test/cursor_test.rb +93 -32
  49. data/test/db_api_test.rb +28 -55
  50. data/test/db_connection_test.rb +2 -3
  51. data/test/db_test.rb +45 -40
  52. data/test/grid_file_system_test.rb +14 -6
  53. data/test/grid_io_test.rb +36 -7
  54. data/test/grid_test.rb +54 -10
  55. data/test/replica_sets/connect_test.rb +84 -0
  56. data/test/replica_sets/count_test.rb +35 -0
  57. data/test/{replica → replica_sets}/insert_test.rb +17 -14
  58. data/test/replica_sets/pooled_insert_test.rb +55 -0
  59. data/test/replica_sets/query_secondaries.rb +80 -0
  60. data/test/replica_sets/query_test.rb +41 -0
  61. data/test/replica_sets/replication_ack_test.rb +64 -0
  62. data/test/replica_sets/rs_test_helper.rb +29 -0
  63. data/test/safe_test.rb +68 -0
  64. data/test/support/hash_with_indifferent_access.rb +199 -0
  65. data/test/support/keys.rb +45 -0
  66. data/test/support_test.rb +19 -0
  67. data/test/test_helper.rb +53 -15
  68. data/test/threading/{test_threading_large_pool.rb → threading_with_large_pool_test.rb} +2 -2
  69. data/test/threading_test.rb +2 -2
  70. data/test/tools/repl_set_manager.rb +241 -0
  71. data/test/tools/test.rb +13 -0
  72. data/test/unit/collection_test.rb +70 -7
  73. data/test/unit/connection_test.rb +18 -39
  74. data/test/unit/cursor_test.rb +7 -8
  75. data/test/unit/db_test.rb +14 -17
  76. data/test/unit/grid_test.rb +49 -0
  77. data/test/unit/pool_test.rb +9 -0
  78. data/test/unit/repl_set_connection_test.rb +82 -0
  79. data/test/unit/safe_test.rb +125 -0
  80. metadata +132 -51
  81. data/bin/bson_benchmark.rb +0 -59
  82. data/bin/fail_if_no_c.rb +0 -11
  83. data/examples/admin.rb +0 -43
  84. data/examples/capped.rb +0 -22
  85. data/examples/cursor.rb +0 -48
  86. data/examples/gridfs.rb +0 -44
  87. data/examples/index_test.rb +0 -126
  88. data/examples/info.rb +0 -31
  89. data/examples/queries.rb +0 -70
  90. data/examples/simple.rb +0 -24
  91. data/examples/strict.rb +0 -35
  92. data/examples/types.rb +0 -36
  93. data/test/replica/count_test.rb +0 -34
  94. data/test/replica/pooled_insert_test.rb +0 -54
  95. data/test/replica/query_test.rb +0 -39
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  # --
2
4
  # Copyright (C) 2008-2010 10gen Inc.
3
5
  #
@@ -12,20 +14,27 @@
12
14
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
15
  # See the License for the specific language governing permissions and
14
16
  # limitations under the License.
15
- # ++
16
17
 
17
18
  module Mongo
18
19
 
19
20
  # A named collection of documents in a database.
20
21
  class Collection
21
22
 
22
- attr_reader :db, :name, :pk_factory, :hint
23
+ attr_reader :db, :name, :pk_factory, :hint, :safe
23
24
 
24
25
  # Initialize a collection object.
25
26
  #
26
27
  # @param [DB] db a MongoDB database instance.
27
28
  # @param [String, Symbol] name the name of the collection.
28
29
  #
30
+ # @option options [:create_pk] :pk (BSON::ObjectId) A primary key factory to use
31
+ # other than the default BSON::ObjectId.
32
+ #
33
+ # @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
34
+ # for insert, update, and remove method called on this Collection instance. If no
35
+ # value is provided, the default value set on this instance's DB will be used. This
36
+ # default can be overridden for any invocation of insert, update, or remove.
37
+ #
29
38
  # @raise [InvalidNSName]
30
39
  # if collection name is empty, contains '$', or starts or ends with '.'
31
40
  #
@@ -35,7 +44,7 @@ module Mongo
35
44
  # @return [Collection]
36
45
  #
37
46
  # @core collections constructor_details
38
- def initialize(db, name, pk_factory=nil)
47
+ def initialize(db, name, options={})
39
48
  case name
40
49
  when Symbol, String
41
50
  else
@@ -54,9 +63,23 @@ module Mongo
54
63
  raise Mongo::InvalidNSName, "collection names must not start or end with '.'"
55
64
  end
56
65
 
66
+ if options.respond_to?(:create_pk) || !options.is_a?(Hash)
67
+ warn "The method for specifying a primary key factory on a Collection has changed.\n" +
68
+ "Please specify it as an option (e.g., :pk => PkFactory)."
69
+ pk_factory = options
70
+ else
71
+ pk_factory = nil
72
+ end
73
+
57
74
  @db, @name = db, name
58
75
  @connection = @db.connection
59
- @pk_factory = pk_factory || BSON::ObjectID
76
+ @logger = @connection.logger
77
+ @cache_time = @db.cache_time
78
+ @cache = Hash.new(0)
79
+ unless pk_factory
80
+ @safe = options.has_key?(:safe) ? options[:safe] : @db.safe
81
+ end
82
+ @pk_factory = pk_factory || options[:pk] || BSON::ObjectId
60
83
  @hint = nil
61
84
  end
62
85
 
@@ -105,7 +128,10 @@ module Mongo
105
128
  #
106
129
  # @param [Hash] selector
107
130
  # a document specifying elements which must be present for a
108
- # document to be included in the result set.
131
+ # document to be included in the result set. Note that in rare cases,
132
+ # (e.g., with $near queries), the order of keys will matter. To preserve
133
+ # key order on a selector, use an instance of BSON::OrderedHash (only applies
134
+ # to Ruby 1.8).
109
135
  #
110
136
  # @option opts [Array, Hash] :fields field names that should be returned in the result
111
137
  # set ("_id" will always be included). By limiting results to a certain subset of fields,
@@ -117,12 +143,12 @@ module Mongo
117
143
  # @option opts [Array] :sort an array of [key, direction] pairs to sort by. Direction should
118
144
  # be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
119
145
  # @option opts [String, Array, OrderedHash] :hint hint for query optimizer, usually not necessary if using MongoDB > 1.1
120
- # @option opts [Boolean] :snapshot ('false') if true, snapshot mode will be used for this query.
146
+ # @option opts [Boolean] :snapshot (false) if true, snapshot mode will be used for this query.
121
147
  # Snapshot mode assures no duplicates are returned, or objects missed, which were preset at both the start and
122
148
  # end of the query's execution. For details see http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
123
149
  # @option opts [Boolean] :batch_size (100) the number of documents to returned by the database per GETMORE operation. A value of 0
124
150
  # will let the database server decide how many results to returns. This option can be ignored for most use cases.
125
- # @option opts [Boolean] :timeout ('true') when +true+, the returned cursor will be subject to
151
+ # @option opts [Boolean] :timeout (true) when +true+, the returned cursor will be subject to
126
152
  # the normal cursor timeout behavior of the mongod process. When +false+, the returned cursor will never timeout. Note
127
153
  # that disabling timeout will only work when #find is invoked with a block. This is to prevent any inadvertant failure to
128
154
  # close the cursor, as the cursor is explicitly closed when block code finishes.
@@ -141,24 +167,28 @@ module Mongo
141
167
  limit = opts.delete(:limit) || 0
142
168
  sort = opts.delete(:sort)
143
169
  hint = opts.delete(:hint)
144
- snapshot = opts.delete(:snapshot)
170
+ snapshot = opts.delete(:snapshot)
145
171
  batch_size = opts.delete(:batch_size)
146
- if opts[:timeout] == false && !block_given?
147
- raise ArgumentError, "Timeout can be set to false only when #find is invoked with a block."
172
+ timeout = (opts.delete(:timeout) == false) ? false : true
173
+
174
+ if timeout == false && !block_given?
175
+ raise ArgumentError, "Collection#find must be invoked with a block when timeout is disabled."
148
176
  end
149
- timeout = block_given? ? (opts.delete(:timeout) || true) : true
177
+
150
178
  if hint
151
179
  hint = normalize_hint_fields(hint)
152
180
  else
153
181
  hint = @hint # assumed to be normalized already
154
182
  end
183
+
155
184
  raise RuntimeError, "Unknown options [#{opts.inspect}]" unless opts.empty?
156
185
 
157
186
  cursor = Cursor.new(self, :selector => selector, :fields => fields, :skip => skip, :limit => limit,
158
- :order => sort, :hint => hint, :snapshot => snapshot, :timeout => timeout, :batch_size => batch_size)
187
+ :order => sort, :hint => hint, :snapshot => snapshot, :timeout => timeout, :batch_size => batch_size)
188
+
159
189
  if block_given?
160
190
  yield cursor
161
- cursor.close()
191
+ cursor.close
162
192
  nil
163
193
  else
164
194
  cursor
@@ -170,9 +200,9 @@ module Mongo
170
200
  # @return [OrderedHash, Nil]
171
201
  # a single document or nil if no result is found.
172
202
  #
173
- # @param [Hash, ObjectID, Nil] spec_or_object_id a hash specifying elements
203
+ # @param [Hash, ObjectId, Nil] spec_or_object_id a hash specifying elements
174
204
  # which must be present for a document to be included in the result set or an
175
- # instance of ObjectID to be used as the value for an _id query.
205
+ # instance of ObjectId to be used as the value for an _id query.
176
206
  # If nil, an empty selector, {}, will be used.
177
207
  #
178
208
  # @option opts [Hash]
@@ -184,12 +214,12 @@ module Mongo
184
214
  spec = case spec_or_object_id
185
215
  when nil
186
216
  {}
187
- when BSON::ObjectID
217
+ when BSON::ObjectId
188
218
  {:_id => spec_or_object_id}
189
219
  when Hash
190
220
  spec_or_object_id
191
221
  else
192
- raise TypeError, "spec_or_object_id must be an instance of ObjectID or Hash, or nil"
222
+ raise TypeError, "spec_or_object_id must be an instance of ObjectId or Hash, or nil"
193
223
  end
194
224
  find(spec, opts.merge(:limit => -1)).next_document
195
225
  end
@@ -201,19 +231,24 @@ module Mongo
201
231
  # then an update (upsert) operation will be performed, and any existing
202
232
  # document with that _id is overwritten. Otherwise an insert operation is performed.
203
233
  #
204
- # @return [ObjectID] the _id of the saved document.
234
+ # @return [ObjectId] the _id of the saved document.
205
235
  #
206
- # @option opts [Boolean] :safe (+false+)
207
- # If true, check that the save succeeded. OperationFailure
208
- # will be raised on an error. Note that a safe check requires an extra
209
- # round-trip to the database.
210
- def save(doc, options={})
236
+ # @option opts [Boolean, Hash] :safe (+false+)
237
+ # run the operation in safe mode, which run a getlasterror command on the
238
+ # database to report any assertion. In addition, a hash can be provided to
239
+ # run an fsync and/or wait for replication of the save (>= 1.5.1). See the options
240
+ # for DB#error.
241
+ #
242
+ # @raise [OperationFailure] when :safe mode fails.
243
+ #
244
+ # @see DB#remove for options that can be passed to :safe.
245
+ def save(doc, opts={})
211
246
  if doc.has_key?(:_id) || doc.has_key?('_id')
212
247
  id = doc[:_id] || doc['_id']
213
- update({:_id => id}, doc, :upsert => true, :safe => options.delete(:safe))
248
+ update({:_id => id}, doc, :upsert => true, :safe => opts.fetch(:safe, @safe))
214
249
  id
215
250
  else
216
- insert(doc, :safe => options.delete(:safe))
251
+ insert(doc, :safe => opts.fetch(:safe, @safe))
217
252
  end
218
253
  end
219
254
 
@@ -222,20 +257,25 @@ module Mongo
222
257
  # @param [Hash, Array] doc_or_docs
223
258
  # a document (as a hash) or array of documents to be inserted.
224
259
  #
225
- # @return [ObjectID, Array]
226
- # the _id of the inserted document or a list of _ids of all inserted documents.
227
- # Note: the object may have been modified by the database's PK factory, if it has one.
260
+ # @return [ObjectId, Array]
261
+ # The _id of the inserted document or a list of _ids of all inserted documents.
228
262
  #
229
- # @option opts [Boolean] :safe (+false+)
230
- # If true, check that the save succeeded. OperationFailure
231
- # will be raised on an error. Note that a safe check requires an extra
232
- # round-trip to the database.
263
+ # @option opts [Boolean, Hash] :safe (+false+)
264
+ # run the operation in safe mode, which run a getlasterror command on the
265
+ # database to report any assertion. In addition, a hash can be provided to
266
+ # run an fsync and/or wait for replication of the insert (>= 1.5.1). Safe
267
+ # options provided here will override any safe options set on this collection,
268
+ # its database object, or the current connection. See the options on
269
+ # for DB#get_last_error.
270
+ #
271
+ # @see DB#remove for options that can be passed to :safe.
233
272
  #
234
273
  # @core insert insert-instance_method
235
274
  def insert(doc_or_docs, options={})
236
275
  doc_or_docs = [doc_or_docs] unless doc_or_docs.is_a?(Array)
237
276
  doc_or_docs.collect! { |doc| @pk_factory.create_pk(doc) }
238
- result = insert_documents(doc_or_docs, @name, true, options[:safe])
277
+ safe = options.has_key?(:safe) ? options[:safe] : @safe
278
+ result = insert_documents(doc_or_docs, @name, true, safe)
239
279
  result.size > 1 ? result : result.first
240
280
  end
241
281
  alias_method :<<, :insert
@@ -245,8 +285,12 @@ module Mongo
245
285
  # @param [Hash] selector
246
286
  # If specified, only matching documents will be removed.
247
287
  #
248
- # @option opts [Boolean] :safe [false] run the operation in safe mode, which
249
- # will call :getlasterror on the database and report any assertions.
288
+ # @option opts [Boolean, Hash] :safe (+false+)
289
+ # run the operation in safe mode, which will run a getlasterror command on the
290
+ # database to report any assertion. In addition, a hash can be provided to
291
+ # run an fsync and/or wait for replication of the remove (>= 1.5.1). Safe
292
+ # options provided here will override any safe options set on this collection,
293
+ # its database, or the current connection. See the options for DB#get_last_error for more details.
250
294
  #
251
295
  # @example remove all documents from the 'users' collection:
252
296
  # users.remove
@@ -255,32 +299,33 @@ module Mongo
255
299
  # @example remove only documents that have expired:
256
300
  # users.remove({:expire => {"$lte" => Time.now}})
257
301
  #
258
- # @return [True]
302
+ # @return [Hash, true] Returns a Hash containing the last error object if running in safe mode.
303
+ # Otherwise, returns true.
259
304
  #
260
305
  # @raise [Mongo::OperationFailure] an exception will be raised iff safe mode is enabled
261
306
  # and the operation fails.
262
307
  #
308
+ # @see DB#remove for options that can be passed to :safe.
309
+ #
263
310
  # @core remove remove-instance_method
264
311
  def remove(selector={}, opts={})
265
312
  # Initial byte is 0.
266
- message = BSON::ByteBuffer.new([0, 0, 0, 0])
313
+ safe = opts.has_key?(:safe) ? opts[:safe] : @safe
314
+ message = BSON::ByteBuffer.new("\0\0\0\0")
267
315
  BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}")
268
316
  message.put_int(0)
269
- message.put_array(BSON::BSON_CODER.serialize(selector, false, true).to_a)
317
+ message.put_binary(BSON::BSON_CODER.serialize(selector, false, true).to_s)
270
318
 
271
- if opts[:safe]
272
- @connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name,
273
- "#{@db.name}['#{@name}'].remove(#{selector.inspect})")
274
- # the return value of send_message_with_safe_check isn't actually meaningful --
275
- # only the fact that it didn't raise an error is -- so just return true
276
- true
319
+ @logger.debug("MONGODB #{@db.name}['#{@name}'].remove(#{selector.inspect})") if @logger
320
+ if safe
321
+ @connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name, nil, safe)
277
322
  else
278
- @connection.send_message(Mongo::Constants::OP_DELETE, message,
279
- "#{@db.name}['#{@name}'].remove(#{selector.inspect})")
323
+ @connection.send_message(Mongo::Constants::OP_DELETE, message)
324
+ true
280
325
  end
281
326
  end
282
327
 
283
- # Update a single document in this collection.
328
+ # Update one or more documents in this collection.
284
329
  #
285
330
  # @param [Hash] selector
286
331
  # a hash specifying elements which must be present for a document to be updated. Note:
@@ -297,25 +342,30 @@ module Mongo
297
342
  # @option opts [Boolean] :safe (+false+)
298
343
  # If true, check that the save succeeded. OperationFailure
299
344
  # will be raised on an error. Note that a safe check requires an extra
300
- # round-trip to the database.
345
+ # round-trip to the database. Safe options provided here will override any safe
346
+ # options set on this collection, its database object, or the current collection.
347
+ # See the options for DB#get_last_error for details.
348
+ #
349
+ # @return [Hash, true] Returns a Hash containing the last error object if running in safe mode.
350
+ # Otherwise, returns true.
301
351
  #
302
352
  # @core update update-instance_method
303
353
  def update(selector, document, options={})
304
354
  # Initial byte is 0.
305
- message = BSON::ByteBuffer.new([0, 0, 0, 0])
355
+ safe = options.has_key?(:safe) ? options[:safe] : @safe
356
+ message = BSON::ByteBuffer.new("\0\0\0\0")
306
357
  BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}")
307
358
  update_options = 0
308
359
  update_options += 1 if options[:upsert]
309
360
  update_options += 2 if options[:multi]
310
361
  message.put_int(update_options)
311
- message.put_array(BSON::BSON_CODER.serialize(selector, false, true).to_a)
312
- message.put_array(BSON::BSON_CODER.serialize(document, false, true).to_a)
313
- if options[:safe]
314
- @connection.send_message_with_safe_check(Mongo::Constants::OP_UPDATE, message, @db.name,
315
- "#{@db.name}['#{@name}'].update(#{selector.inspect}, #{document.inspect})")
362
+ message.put_binary(BSON::BSON_CODER.serialize(selector, false, true).to_s)
363
+ message.put_binary(BSON::BSON_CODER.serialize(document, false, true).to_s)
364
+ @logger.debug("MONGODB #{@db.name}['#{@name}'].update(#{selector.inspect}, #{document.inspect})") if @logger
365
+ if safe
366
+ @connection.send_message_with_safe_check(Mongo::Constants::OP_UPDATE, message, @db.name, nil, safe)
316
367
  else
317
- @connection.send_message(Mongo::Constants::OP_UPDATE, message,
318
- "#{@db.name}['#{@name}'].update(#{selector.inspect}, #{document.inspect})")
368
+ @connection.send_message(Mongo::Constants::OP_UPDATE, message, nil)
319
369
  end
320
370
  end
321
371
 
@@ -333,13 +383,16 @@ module Mongo
333
383
  # Also note that it is permissible to create compound indexes that include a geospatial index as
334
384
  # long as the geospatial index comes first.
335
385
  #
386
+ # If your code calls create_index frequently, you can use Collection#ensure_index to cache these calls
387
+ # and thereby prevent excessive round trips to the database.
388
+ #
336
389
  # @option opts [Boolean] :unique (false) if true, this index will enforce a uniqueness constraint.
337
390
  # @option opts [Boolean] :background (false) indicate that the index should be built in the background. This
338
391
  # feature is only available in MongoDB >= 1.3.2.
339
- # @option opts [Boolean] :dropDups If creating a unique index on a collection with pre-existing records,
392
+ # @option opts [Boolean] :drop_dups (nil) If creating a unique index on a collection with pre-existing records,
340
393
  # this option will keep the first document the database indexes and drop all subsequent with duplicate values.
341
- # @option opts [Integer] :min specify the minimum longitude and latitude for a geo index.
342
- # @option opts [Integer] :max specify the maximum longitude and latitude for a geo index.
394
+ # @option opts [Integer] :min (nil) specify the minimum longitude and latitude for a geo index.
395
+ # @option opts [Integer] :max (nil) specify the maximum longitude and latitude for a geo index.
343
396
  #
344
397
  # @example Creating a compound index:
345
398
  # @posts.create_index([['subject', Mongo::ASCENDING], ['created_at', Mongo::DESCENDING]])
@@ -359,37 +412,46 @@ module Mongo
359
412
  #
360
413
  # @core indexes create_index-instance_method
361
414
  def create_index(spec, opts={})
362
- opts.assert_valid_keys(:min, :max, :background, :unique, :dropDups)
363
- field_spec = OrderedHash.new
364
- if spec.is_a?(String) || spec.is_a?(Symbol)
365
- field_spec[spec.to_s] = 1
366
- elsif spec.is_a?(Array) && spec.all? {|field| field.is_a?(Array) }
367
- spec.each do |f|
368
- if [Mongo::ASCENDING, Mongo::DESCENDING, Mongo::GEO2D].include?(f[1])
369
- field_spec[f[0].to_s] = f[1]
370
- else
371
- raise MongoArgumentError, "Invalid index field #{f[1].inspect}; " +
372
- "should be one of Mongo::ASCENDING (1), Mongo::DESCENDING (-1) or Mongo::GEO2D ('2d')."
373
- end
374
- end
375
- else
376
- raise MongoArgumentError, "Invalid index specification #{spec.inspect}; " +
377
- "should be either a string, symbol, or an array of arrays."
378
- end
415
+ opts[:dropDups] = opts.delete(:drop_dups) if opts[:drop_dups]
416
+ field_spec = parse_index_spec(spec)
417
+ name = opts.delete(:name) || generate_index_name(field_spec)
379
418
 
380
- name = generate_index_name(field_spec)
419
+ generate_indexes(field_spec, name, opts)
420
+ name
421
+ end
381
422
 
382
- selector = {
383
- :name => name,
384
- :ns => "#{@db.name}.#{@name}",
385
- :key => field_spec
386
- }
387
- selector.merge!(opts)
388
- begin
389
- response = insert_documents([selector], Mongo::DB::SYSTEM_INDEX_COLLECTION, false, true)
390
- rescue Mongo::OperationFailure
391
- raise Mongo::OperationFailure, "Failed to create index #{selector.inspect} with the following errors: #{response}"
423
+ # Calls create_index and sets a flag to not do so again for another X minutes.
424
+ # this time can be specified as an option when initializing a Mongo::DB object as options[:cache_time]
425
+ # Any changes to an index will be propogated through regardless of cache time (e.g., a change of index direction)
426
+ #
427
+ # The parameters and options for this methods are the same as those for Collection#create_index.
428
+ #
429
+ # @example Call sequence:
430
+ # Time t: @posts.ensure_index([['subject', Mongo::ASCENDING]) -- calls create_index and
431
+ # sets the 5 minute cache
432
+ # Time t+2min : @posts.ensure_index([['subject', Mongo::ASCENDING]) -- doesn't do anything
433
+ # Time t+3min : @posts.ensure_index([['something_else', Mongo::ASCENDING]) -- calls create_index
434
+ # and sets 5 minute cache
435
+ # Time t+10min : @posts.ensure_index([['subject', Mongo::ASCENDING]) -- calls create_index and
436
+ # resets the 5 minute counter
437
+ #
438
+ # @return [String] the name of the index.
439
+ def ensure_index(spec, opts={})
440
+ valid = BSON::OrderedHash.new
441
+ now = Time.now.utc.to_i
442
+ field_spec = parse_index_spec(spec)
443
+
444
+ field_spec.each do |key, value|
445
+ cache_key = generate_index_name({key => value})
446
+ timeout = @cache[cache_key] || 0
447
+ valid[key] = value if timeout <= now
392
448
  end
449
+
450
+ name = opts.delete(:name) || generate_index_name(valid)
451
+ generate_indexes(valid, name, opts) if valid.any?
452
+
453
+ # Reset the cache here in case there are any errors inserting. Best to be safe.
454
+ @cache[name] = now + @cache_time
393
455
  name
394
456
  end
395
457
 
@@ -399,6 +461,7 @@ module Mongo
399
461
  #
400
462
  # @core indexes
401
463
  def drop_index(name)
464
+ @cache[name] = nil
402
465
  @db.drop_index(@name, name)
403
466
  end
404
467
 
@@ -406,6 +469,7 @@ module Mongo
406
469
  #
407
470
  # @core indexes
408
471
  def drop_indexes
472
+ @cache = {}
409
473
 
410
474
  # Note: calling drop_indexes with no args will drop them all.
411
475
  @db.drop_index(@name, '*')
@@ -416,11 +480,10 @@ module Mongo
416
480
  @db.drop_collection(@name)
417
481
  end
418
482
 
419
-
420
483
  # Atomically update and return a document using MongoDB's findAndModify command. (MongoDB > 1.3.0)
421
484
  #
422
- # @option opts [Hash] :update (nil) the update operation to perform on the matched document.
423
485
  # @option opts [Hash] :query ({}) a query selector document for matching the desired document.
486
+ # @option opts [Hash] :update (nil) the update operation to perform on the matched document.
424
487
  # @option opts [Array, String, OrderedHash] :sort ({}) specify a sort option for the query using any
425
488
  # of the sort options available for Cursor#sort. Sort order is important if the query will be matching
426
489
  # multiple documents since only the first matching document will be updated and returned.
@@ -432,12 +495,12 @@ module Mongo
432
495
  #
433
496
  # @core findandmodify find_and_modify-instance_method
434
497
  def find_and_modify(opts={})
435
- cmd = OrderedHash.new
498
+ cmd = BSON::OrderedHash.new
436
499
  cmd[:findandmodify] = @name
437
500
  cmd.merge!(opts)
438
501
  cmd[:sort] = Mongo::Support.format_order_clause(opts[:sort]) if opts[:sort]
439
502
 
440
- @db.command(cmd, false, true)['value']
503
+ @db.command(cmd)['value']
441
504
  end
442
505
 
443
506
  # Perform a map/reduce operation on the current collection.
@@ -455,6 +518,8 @@ module Mongo
455
518
  # @option opts [String] :out (nil) the name of the output collection. If specified, the collection will not be treated as temporary.
456
519
  # @option opts [Boolean] :keeptemp (false) if true, the generated collection will be persisted. default is false.
457
520
  # @option opts [Boolean ] :verbose (false) if true, provides statistics on job execution time.
521
+ # @option opts [Boolean] :raw (false) if true, return the raw result object from the map_reduce command, and not
522
+ # the instantiated collection that's returned by default.
458
523
  #
459
524
  # @return [Collection] a collection containing the results of the operation.
460
525
  #
@@ -464,18 +529,24 @@ module Mongo
464
529
  def map_reduce(map, reduce, opts={})
465
530
  map = BSON::Code.new(map) unless map.is_a?(BSON::Code)
466
531
  reduce = BSON::Code.new(reduce) unless reduce.is_a?(BSON::Code)
532
+ raw = opts.delete(:raw)
467
533
 
468
- hash = OrderedHash.new
534
+ hash = BSON::OrderedHash.new
469
535
  hash['mapreduce'] = self.name
470
536
  hash['map'] = map
471
537
  hash['reduce'] = reduce
472
538
  hash.merge! opts
473
539
 
474
540
  result = @db.command(hash)
475
- unless result["ok"] == 1
541
+ unless Mongo::Support.ok?(result)
476
542
  raise Mongo::OperationFailure, "map-reduce failed: #{result['errmsg']}"
477
543
  end
478
- @db[result["result"]]
544
+
545
+ if raw
546
+ result
547
+ else
548
+ @db[result["result"]]
549
+ end
479
550
  end
480
551
  alias :mapreduce :map_reduce
481
552
 
@@ -521,9 +592,9 @@ module Mongo
521
592
  group_command['group']['finalize'] = finalize
522
593
  end
523
594
 
524
- result = @db.command group_command
595
+ result = @db.command(group_command)
525
596
 
526
- if result["ok"] == 1
597
+ if Mongo::Support.ok?(result)
527
598
  result["retval"]
528
599
  else
529
600
  raise OperationFailure, "group command failed: #{result['errmsg']}"
@@ -557,7 +628,7 @@ module Mongo
557
628
  # @return [Array] an array of distinct values.
558
629
  def distinct(key, query=nil)
559
630
  raise MongoArgumentError unless [String, Symbol].include?(key.class)
560
- command = OrderedHash.new
631
+ command = BSON::OrderedHash.new
561
632
  command[:distinct] = @name
562
633
  command[:key] = key.to_s
563
634
  command[:query] = query
@@ -568,10 +639,12 @@ module Mongo
568
639
  # Rename this collection.
569
640
  #
570
641
  # Note: If operating in auth mode, the client must be authorized as an admin to
571
- # perform this operation.
642
+ # perform this operation.
572
643
  #
573
644
  # @param [String] new_name the new name for this collection
574
645
  #
646
+ # @return [String] the name of the new collection.
647
+ #
575
648
  # @raise [Mongo::InvalidNSName] if +new_name+ is an invalid collection name.
576
649
  def rename(new_name)
577
650
  case new_name
@@ -593,6 +666,7 @@ module Mongo
593
666
  end
594
667
 
595
668
  @db.rename_collection(@name, new_name)
669
+ @name = new_name
596
670
  end
597
671
 
598
672
  # Get information on the indexes for this collection.
@@ -639,7 +713,7 @@ module Mongo
639
713
  when nil
640
714
  nil
641
715
  else
642
- h = OrderedHash.new
716
+ h = BSON::OrderedHash.new
643
717
  hint.to_a.each { |k| h[k] = 1 }
644
718
  h
645
719
  end
@@ -647,20 +721,66 @@ module Mongo
647
721
 
648
722
  private
649
723
 
724
+ def parse_index_spec(spec)
725
+ field_spec = BSON::OrderedHash.new
726
+ if spec.is_a?(String) || spec.is_a?(Symbol)
727
+ field_spec[spec.to_s] = 1
728
+ elsif spec.is_a?(Array) && spec.all? {|field| field.is_a?(Array) }
729
+ spec.each do |f|
730
+ if [Mongo::ASCENDING, Mongo::DESCENDING, Mongo::GEO2D].include?(f[1])
731
+ field_spec[f[0].to_s] = f[1]
732
+ else
733
+ raise MongoArgumentError, "Invalid index field #{f[1].inspect}; " +
734
+ "should be one of Mongo::ASCENDING (1), Mongo::DESCENDING (-1) or Mongo::GEO2D ('2d')."
735
+ end
736
+ end
737
+ else
738
+ raise MongoArgumentError, "Invalid index specification #{spec.inspect}; " +
739
+ "should be either a string, symbol, or an array of arrays."
740
+ end
741
+ field_spec
742
+ end
743
+
744
+ def generate_indexes(field_spec, name, opts)
745
+ selector = {
746
+ :name => name,
747
+ :ns => "#{@db.name}.#{@name}",
748
+ :key => field_spec
749
+ }
750
+ selector.merge!(opts)
751
+
752
+ begin
753
+ insert_documents([selector], Mongo::DB::SYSTEM_INDEX_COLLECTION, false, true)
754
+
755
+ rescue Mongo::OperationFailure => e
756
+ if selector[:dropDups] && e.message =~ /^11000/
757
+ # NOP. If the user is intentionally dropping dups, we can ignore duplicate key errors.
758
+ else
759
+ raise Mongo::OperationFailure, "Failed to create index #{selector.inspect} with the following error: " +
760
+ "#{e.message}"
761
+ end
762
+ end
763
+
764
+ nil
765
+ end
766
+
650
767
  # Sends a Mongo::Constants::OP_INSERT message to the database.
651
768
  # Takes an array of +documents+, an optional +collection_name+, and a
652
769
  # +check_keys+ setting.
653
770
  def insert_documents(documents, collection_name=@name, check_keys=true, safe=false)
654
771
  # Initial byte is 0.
655
- message = BSON::ByteBuffer.new([0, 0, 0, 0])
772
+ message = BSON::ByteBuffer.new("\0\0\0\0")
656
773
  BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{collection_name}")
657
- documents.each { |doc| message.put_array(BSON::BSON_CODER.serialize(doc, check_keys, true).to_a) }
774
+ documents.each do |doc|
775
+ message.put_binary(BSON::BSON_CODER.serialize(doc, check_keys, true).to_s)
776
+ end
777
+ raise InvalidOperation, "Exceded maximum insert size of 16,000,000 bytes" if message.size > 16_000_000
778
+
779
+ @logger.debug("MONGODB #{@db.name}['#{collection_name}'].insert(#{documents.inspect})") if @logger
658
780
  if safe
659
- @connection.send_message_with_safe_check(Mongo::Constants::OP_INSERT, message, @db.name,
660
- "#{@db.name}['#{collection_name}'].insert(#{documents.inspect})")
781
+ @connection.send_message_with_safe_check(Mongo::Constants::OP_INSERT, message, @db.name, nil, safe)
661
782
  else
662
- @connection.send_message(Mongo::Constants::OP_INSERT, message,
663
- "#{@db.name}['#{collection_name}'].insert(#{documents.inspect})")
783
+ @connection.send_message(Mongo::Constants::OP_INSERT, message, nil)
664
784
  end
665
785
  documents.collect { |o| o[:_id] || o['_id'] }
666
786
  end