kbaum-mongo 0.18.3p

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/LICENSE.txt +202 -0
  2. data/README.rdoc +339 -0
  3. data/Rakefile +138 -0
  4. data/bin/bson_benchmark.rb +59 -0
  5. data/bin/fail_if_no_c.rb +11 -0
  6. data/examples/admin.rb +42 -0
  7. data/examples/capped.rb +22 -0
  8. data/examples/cursor.rb +48 -0
  9. data/examples/gridfs.rb +88 -0
  10. data/examples/index_test.rb +126 -0
  11. data/examples/info.rb +31 -0
  12. data/examples/queries.rb +70 -0
  13. data/examples/simple.rb +24 -0
  14. data/examples/strict.rb +35 -0
  15. data/examples/types.rb +36 -0
  16. data/lib/mongo/collection.rb +609 -0
  17. data/lib/mongo/connection.rb +672 -0
  18. data/lib/mongo/cursor.rb +403 -0
  19. data/lib/mongo/db.rb +555 -0
  20. data/lib/mongo/exceptions.rb +66 -0
  21. data/lib/mongo/gridfs/chunk.rb +91 -0
  22. data/lib/mongo/gridfs/grid.rb +79 -0
  23. data/lib/mongo/gridfs/grid_file_system.rb +101 -0
  24. data/lib/mongo/gridfs/grid_io.rb +338 -0
  25. data/lib/mongo/gridfs/grid_store.rb +580 -0
  26. data/lib/mongo/gridfs.rb +25 -0
  27. data/lib/mongo/types/binary.rb +52 -0
  28. data/lib/mongo/types/code.rb +36 -0
  29. data/lib/mongo/types/dbref.rb +40 -0
  30. data/lib/mongo/types/min_max_keys.rb +58 -0
  31. data/lib/mongo/types/objectid.rb +180 -0
  32. data/lib/mongo/types/regexp_of_holding.rb +45 -0
  33. data/lib/mongo/util/bson_c.rb +18 -0
  34. data/lib/mongo/util/bson_ruby.rb +606 -0
  35. data/lib/mongo/util/byte_buffer.rb +222 -0
  36. data/lib/mongo/util/conversions.rb +87 -0
  37. data/lib/mongo/util/ordered_hash.rb +140 -0
  38. data/lib/mongo/util/server_version.rb +69 -0
  39. data/lib/mongo/util/support.rb +26 -0
  40. data/lib/mongo.rb +63 -0
  41. data/mongo-ruby-driver.gemspec +28 -0
  42. data/test/auxillary/autoreconnect_test.rb +42 -0
  43. data/test/binary_test.rb +15 -0
  44. data/test/bson_test.rb +427 -0
  45. data/test/byte_buffer_test.rb +81 -0
  46. data/test/chunk_test.rb +82 -0
  47. data/test/collection_test.rb +515 -0
  48. data/test/connection_test.rb +160 -0
  49. data/test/conversions_test.rb +120 -0
  50. data/test/cursor_test.rb +379 -0
  51. data/test/db_api_test.rb +780 -0
  52. data/test/db_connection_test.rb +16 -0
  53. data/test/db_test.rb +272 -0
  54. data/test/grid_file_system_test.rb +210 -0
  55. data/test/grid_io_test.rb +78 -0
  56. data/test/grid_store_test.rb +334 -0
  57. data/test/grid_test.rb +87 -0
  58. data/test/objectid_test.rb +125 -0
  59. data/test/ordered_hash_test.rb +172 -0
  60. data/test/replica/count_test.rb +34 -0
  61. data/test/replica/insert_test.rb +50 -0
  62. data/test/replica/pooled_insert_test.rb +54 -0
  63. data/test/replica/query_test.rb +39 -0
  64. data/test/slave_connection_test.rb +36 -0
  65. data/test/test_helper.rb +42 -0
  66. data/test/threading/test_threading_large_pool.rb +90 -0
  67. data/test/threading_test.rb +87 -0
  68. data/test/unit/collection_test.rb +61 -0
  69. data/test/unit/connection_test.rb +117 -0
  70. data/test/unit/cursor_test.rb +93 -0
  71. data/test/unit/db_test.rb +98 -0
  72. metadata +127 -0
@@ -0,0 +1,609 @@
1
+ # --
2
+ # Copyright (C) 2008-2010 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ module Mongo
18
+
19
+ # A named collection of documents in a database.
20
+ class Collection
21
+
22
+ attr_reader :db, :name, :pk_factory, :hint
23
+
24
+ # Initialize a collection object.
25
+ #
26
+ # @param [DB] db a MongoDB database instance.
27
+ # @param [String, Symbol] name the name of the collection.
28
+ #
29
+ # @raise [InvalidName]
30
+ # if collection name is empty, contains '$', or starts or ends with '.'
31
+ #
32
+ # @raise [TypeError]
33
+ # if collection name is not a string or symbol
34
+ #
35
+ # @return [Collection]
36
+ #
37
+ # @core collections constructor_details
38
+ def initialize(db, name, pk_factory=nil)
39
+ case name
40
+ when Symbol, String
41
+ else
42
+ raise TypeError, "new_name must be a string or symbol"
43
+ end
44
+
45
+ name = name.to_s
46
+
47
+ if name.empty? or name.include? ".."
48
+ raise InvalidName, "collection names cannot be empty"
49
+ end
50
+ if name.include? "$"
51
+ raise InvalidName, "collection names must not contain '$'" unless name =~ /((^\$cmd)|(oplog\.\$main))/
52
+ end
53
+ if name.match(/^\./) or name.match(/\.$/)
54
+ raise InvalidName, "collection names must not start or end with '.'"
55
+ end
56
+
57
+ @db, @name = db, name
58
+ @connection = @db.connection
59
+ @pk_factory = pk_factory || ObjectID
60
+ @hint = nil
61
+ end
62
+
63
+ # Return a sub-collection of this collection by name. If 'users' is a collection, then
64
+ # 'users.comments' is a sub-collection of users.
65
+ #
66
+ # @param [String] name
67
+ # the collection to return
68
+ #
69
+ # @raise [InvalidName]
70
+ # if passed an invalid collection name
71
+ #
72
+ # @return [Collection]
73
+ # the specified sub-collection
74
+ def [](name)
75
+ name = "#{self.name}.#{name}"
76
+ return Collection.new(db, name) if !db.strict? || db.collection_names.include?(name)
77
+ raise "Collection #{name} doesn't exist. Currently in strict mode."
78
+ end
79
+
80
+ # Set a hint field for query optimizer. Hint may be a single field
81
+ # name, array of field names, or a hash (preferably an [OrderedHash]).
82
+ # If using MongoDB > 1.1, you probably don't ever need to set a hint.
83
+ #
84
+ # @param [String, Array, OrderedHash] hint a single field, an array of
85
+ # fields, or a hash specifying fields
86
+ def hint=(hint=nil)
87
+ @hint = normalize_hint_fields(hint)
88
+ self
89
+ end
90
+
91
+ # Query the database.
92
+ #
93
+ # The +selector+ argument is a prototype document that all results must
94
+ # match. For example:
95
+ #
96
+ # collection.find({"hello" => "world"})
97
+ #
98
+ # only matches documents that have a key "hello" with value "world".
99
+ # Matches can have other keys *in addition* to "hello".
100
+ #
101
+ # If given an optional block +find+ will yield a Cursor to that block,
102
+ # close the cursor, and then return nil. This guarantees that partially
103
+ # evaluated cursors will be closed. If given no block +find+ returns a
104
+ # cursor.
105
+ #
106
+ # @param [Hash] selector
107
+ # a document specifying elements which must be present for a
108
+ # document to be included in the result set.
109
+ #
110
+ # @option opts [Array] :fields field names that should be returned in the result
111
+ # set ("_id" will always be included). By limiting results to a certain subset of fields,
112
+ # you can cut down on network traffic and decoding time.
113
+ # @option opts [Integer] :skip number of documents to skip from the beginning of the result set
114
+ # @option opts [Integer] :limit maximum number of documents to return
115
+ # @option opts [Array] :sort an array of [key, direction] pairs to sort by. Direction should
116
+ # be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
117
+ # @option opts [String, Array, OrderedHash] :hint hint for query optimizer, usually not necessary if using MongoDB > 1.1
118
+ # @option opts [Boolean] :snapshot ('false') if true, snapshot mode will be used for this query.
119
+ # Snapshot mode assures no duplicates are returned, or objects missed, which were preset at both the start and
120
+ # end of the query's execution. For details see http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
121
+ # @option opts [Boolean] :timeout ('true') when +true+, the returned cursor will be subject to
122
+ # the normal cursor timeout behavior of the mongod process. When +false+, the returned cursor will never timeout. Note
123
+ # that disabling timeout will only work when #find is invoked with a block. This is to prevent any inadvertant failure to
124
+ # close the cursor, as the cursor is explicitly closed when block code finishes.
125
+ #
126
+ # @raise [ArgumentError]
127
+ # if timeout is set to false and find is not invoked in a block
128
+ #
129
+ # @raise [RuntimeError]
130
+ # if given unknown options
131
+ #
132
+ # @core find find-instance_method
133
+ def find(selector={}, opts={})
134
+ fields = opts.delete(:fields)
135
+ fields = ["_id"] if fields && fields.empty?
136
+ skip = opts.delete(:skip) || skip || 0
137
+ limit = opts.delete(:limit) || 0
138
+ sort = opts.delete(:sort)
139
+ hint = opts.delete(:hint)
140
+ snapshot = opts.delete(:snapshot)
141
+ if opts[:timeout] == false && !block_given?
142
+ raise ArgumentError, "Timeout can be set to false only when #find is invoked with a block."
143
+ end
144
+ timeout = block_given? ? (opts.delete(:timeout) || true) : true
145
+ if hint
146
+ hint = normalize_hint_fields(hint)
147
+ else
148
+ hint = @hint # assumed to be normalized already
149
+ end
150
+ raise RuntimeError, "Unknown options [#{opts.inspect}]" unless opts.empty?
151
+
152
+ cursor = Cursor.new(self, :selector => selector, :fields => fields, :skip => skip, :limit => limit,
153
+ :order => sort, :hint => hint, :snapshot => snapshot, :timeout => timeout)
154
+ if block_given?
155
+ yield cursor
156
+ cursor.close()
157
+ nil
158
+ else
159
+ cursor
160
+ end
161
+ end
162
+
163
+ # Return a single object from the database.
164
+ #
165
+ # @return [OrderedHash, Nil]
166
+ # a single document or nil if no result is found.
167
+ #
168
+ # @param [Hash, ObjectID, Nil] spec_or_object_id a hash specifying elements
169
+ # which must be present for a document to be included in the result set or an
170
+ # instance of ObjectID to be used as the value for an _id query.
171
+ # If nil, an empty selector, {}, will be used.
172
+ #
173
+ # @option opts [Hash]
174
+ # any valid options that can be send to Collection#find
175
+ #
176
+ # @raise [TypeError]
177
+ # if the argument is of an improper type.
178
+ def find_one(spec_or_object_id=nil, opts={})
179
+ spec = case spec_or_object_id
180
+ when nil
181
+ {}
182
+ when ObjectID
183
+ {:_id => spec_or_object_id}
184
+ when Hash
185
+ spec_or_object_id
186
+ else
187
+ raise TypeError, "spec_or_object_id must be an instance of ObjectID or Hash, or nil"
188
+ end
189
+ find(spec, opts.merge(:limit => -1)).next_document
190
+ end
191
+
192
+ # Save a document to this collection.
193
+ #
194
+ # @param [Hash] doc
195
+ # the document to be saved. If the document already has an '_id' key,
196
+ # then an update (upsert) operation will be performed, and any existing
197
+ # document with that _id is overwritten. Otherwise an insert operation is performed.
198
+ #
199
+ # @return [ObjectID] the _id of the saved document.
200
+ #
201
+ # @option opts [Boolean] :safe (+false+)
202
+ # If true, check that the save succeeded. OperationFailure
203
+ # will be raised on an error. Note that a safe check requires an extra
204
+ # round-trip to the database.
205
+ def save(doc, options={})
206
+ if doc.has_key?(:_id) || doc.has_key?('_id')
207
+ id = doc[:_id] || doc['_id']
208
+ update({:_id => id}, doc, :upsert => true, :safe => options.delete(:safe))
209
+ id
210
+ else
211
+ insert(doc, :safe => options.delete(:safe))
212
+ end
213
+ end
214
+
215
+ # Insert one or more documents into the collection.
216
+ #
217
+ # @param [Hash, Array] doc_or_docs
218
+ # a document (as a hash) or array of documents to be inserted.
219
+ #
220
+ # @return [ObjectID, Array]
221
+ # the _id of the inserted document or a list of _ids of all inserted documents.
222
+ # Note: the object may have been modified by the database's PK factory, if it has one.
223
+ #
224
+ # @option opts [Boolean] :safe (+false+)
225
+ # If true, check that the save succeeded. OperationFailure
226
+ # will be raised on an error. Note that a safe check requires an extra
227
+ # round-trip to the database.
228
+ #
229
+ # @core insert insert-instance_method
230
+ def insert(doc_or_docs, options={})
231
+ doc_or_docs = [doc_or_docs] unless doc_or_docs.is_a?(Array)
232
+ doc_or_docs.collect! { |doc| @pk_factory.create_pk(doc) }
233
+ result = insert_documents(doc_or_docs, @name, true, options[:safe])
234
+ result.size > 1 ? result : result.first
235
+ end
236
+ alias_method :<<, :insert
237
+
238
+ # Remove all documents from this collection.
239
+ #
240
+ # @param [Hash] selector
241
+ # If specified, only matching documents will be removed.
242
+ #
243
+ # @option opts [Boolean] :safe [false] run the operation in safe mode, which
244
+ # will call :getlasterror on the database and report any assertions.
245
+ #
246
+ # @example remove all documents from the 'users' collection:
247
+ # users.remove
248
+ # users.remove({})
249
+ #
250
+ # @example remove only documents that have expired:
251
+ # users.remove({:expire => {"$lte" => Time.now}})
252
+ #
253
+ # @return [True]
254
+ #
255
+ # @raise [Mongo::OperationFailure] an exception will be raised iff safe mode is enabled
256
+ # and the operation fails.
257
+ #
258
+ # @core remove remove-instance_method
259
+ def remove(selector={}, opts={})
260
+ # Initial byte is 0.
261
+ message = ByteBuffer.new([0, 0, 0, 0])
262
+ BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}")
263
+ message.put_int(0)
264
+ message.put_array(BSON.serialize(selector, false).to_a)
265
+
266
+ if opts[:safe]
267
+ @connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name,
268
+ "db.#{@db.name}.remove(#{selector.inspect})")
269
+ # the return value of send_message_with_safe_check isn't actually meaningful --
270
+ # only the fact that it didn't raise an error is -- so just return true
271
+ true
272
+ else
273
+ @connection.send_message(Mongo::Constants::OP_DELETE, message,
274
+ "db.#{@db.name}.remove(#{selector.inspect})")
275
+ end
276
+ end
277
+
278
+ # Update a single document in this collection.
279
+ #
280
+ # @param [Hash] selector
281
+ # a hash specifying elements which must be present for a document to be updated. Note:
282
+ # the update command currently updates only the first document matching the
283
+ # given selector. If you want all matching documents to be updated, be sure
284
+ # to specify :multi => true.
285
+ # @param [Hash] document
286
+ # a hash specifying the fields to be changed in the selected document,
287
+ # or (in the case of an upsert) the document to be inserted
288
+ #
289
+ # @option [Boolean] :upsert (+false+) if true, performs an upsert (update or insert)
290
+ # @option [Boolean] :multi (+false+) update all documents matching the selector, as opposed to
291
+ # just the first matching document. Note: only works in MongoDB 1.1.3 or later.
292
+ # @option opts [Boolean] :safe (+false+)
293
+ # If true, check that the save succeeded. OperationFailure
294
+ # will be raised on an error. Note that a safe check requires an extra
295
+ # round-trip to the database.
296
+ #
297
+ # @core update update-instance_method
298
+ def update(selector, document, options={})
299
+ # Initial byte is 0.
300
+ message = ByteBuffer.new([0, 0, 0, 0])
301
+ BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}")
302
+ update_options = 0
303
+ update_options += 1 if options[:upsert]
304
+ update_options += 2 if options[:multi]
305
+ message.put_int(update_options)
306
+ message.put_array(BSON.serialize(selector, false).to_a)
307
+ message.put_array(BSON.serialize(document, false, true).to_a)
308
+ if options[:safe]
309
+ @connection.send_message_with_safe_check(Mongo::Constants::OP_UPDATE, message, @db.name,
310
+ "db.#{@name}.update(#{selector.inspect}, #{document.inspect})")
311
+ else
312
+ @connection.send_message(Mongo::Constants::OP_UPDATE, message,
313
+ "db.#{@name}.update(#{selector.inspect}, #{document.inspect})")
314
+ end
315
+ end
316
+
317
+ # Create a new index.
318
+ #
319
+ # @param [String, Array] field_or_spec
320
+ # should be either a single field name or an array of
321
+ # [field name, direction] pairs. Directions should be specified as Mongo::ASCENDING or Mongo::DESCENDING.
322
+ #
323
+ # @param [Boolean] unique if true, this index will enforce a uniqueness constraint.
324
+ #
325
+ # @return [String] the name of the index created.
326
+ #
327
+ # @core indexes create_index-instance_method
328
+ def create_index(field_or_spec, unique=false)
329
+ field_h = OrderedHash.new
330
+ if field_or_spec.is_a?(String) || field_or_spec.is_a?(Symbol)
331
+ field_h[field_or_spec.to_s] = 1
332
+ else
333
+ field_or_spec.each { |f| field_h[f[0].to_s] = f[1] }
334
+ end
335
+ name = generate_index_names(field_h)
336
+ sel = {
337
+ :name => name,
338
+ :ns => "#{@db.name}.#{@name}",
339
+ :key => field_h,
340
+ :unique => unique }
341
+ insert_documents([sel], Mongo::DB::SYSTEM_INDEX_COLLECTION, false)
342
+ name
343
+ end
344
+
345
+ # Drop a specified index.
346
+ #
347
+ # @param [String] name
348
+ #
349
+ # @core indexes
350
+ def drop_index(name)
351
+ @db.drop_index(@name, name)
352
+ end
353
+
354
+ # Drop all indexes.
355
+ #
356
+ # @core indexes
357
+ def drop_indexes
358
+
359
+ # Note: calling drop_indexes with no args will drop them all.
360
+ @db.drop_index(@name, '*')
361
+
362
+ end
363
+
364
+ # Drop the entire collection. USE WITH CAUTION.
365
+ def drop
366
+ @db.drop_collection(@name)
367
+ end
368
+
369
+ # Perform a map/reduce operation on the current collection.
370
+ #
371
+ # @param [String, Code] map a map function, written in JavaScript.
372
+ # @param [String, Code] reduce a reduce function, written in JavaScript.
373
+ #
374
+ # @option opts [Hash] :query ({}) a query selector document, like what's passed to #find, to limit
375
+ # the operation to a subset of the collection.
376
+ # @option opts [Array] :sort ([]) an array of [key, direction] pairs to sort by. Direction should
377
+ # be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
378
+ # @option opts [Integer] :limit (nil) if passing a query, number of objects to return from the collection.
379
+ # @option opts [String, Code] :finalize (nil) a javascript function to apply to the result set after the
380
+ # map/reduce operation has finished.
381
+ # @option opts [String] :out (nil) the name of the output collection. If specified, the collection will not be treated as temporary.
382
+ # @option opts [Boolean] :keeptemp (false) if true, the generated collection will be persisted. default is false.
383
+ # @option opts [Boolean ] :verbose (false) if true, provides statistics on job execution time.
384
+ #
385
+ # @return [Collection] a collection containing the results of the operation.
386
+ #
387
+ # @see http://www.mongodb.org/display/DOCS/MapReduce Offical MongoDB map/reduce documentation.
388
+ #
389
+ # @core mapreduce map_reduce-instance_method
390
+ def map_reduce(map, reduce, opts={})
391
+ map = Code.new(map) unless map.is_a?(Code)
392
+ reduce = Code.new(reduce) unless reduce.is_a?(Code)
393
+
394
+ hash = OrderedHash.new
395
+ hash['mapreduce'] = self.name
396
+ hash['map'] = map
397
+ hash['reduce'] = reduce
398
+ hash.merge! opts
399
+
400
+ result = @db.command(hash)
401
+ unless result["ok"] == 1
402
+ raise Mongo::OperationFailure, "map-reduce failed: #{result['errmsg']}"
403
+ end
404
+ @db[result["result"]]
405
+ end
406
+ alias :mapreduce :map_reduce
407
+
408
+ # Perform a group aggregation.
409
+ #
410
+ # @param [Array, String, Code, Nil] :key either 1) an array of fields to group by,
411
+ # 2) a javascript function to generate the key object, or 3) nil.
412
+ # @param [Hash] condition an optional document specifying a query to limit the documents over which group is run.
413
+ # @param [Hash] initial initial value of the aggregation counter object
414
+ # @param [String, Code] reduce aggregation function, in JavaScript
415
+ # @param [String, Code] finalize :: optional. a JavaScript function that receives and modifies
416
+ # each of the resultant grouped objects. Available only when group is run
417
+ # with command set to true.
418
+ # @param [Nil] deprecated this param in a placeholder for a deprecated param. It will be removed
419
+ # in the next release.
420
+ #
421
+ # @return [Array] the grouped items.
422
+ def group(key, condition, initial, reduce, finalize=nil, deprecated=nil)
423
+
424
+ # Warn of changed API post eval deprecation.
425
+ if finalize == true || finalize == false || deprecated
426
+ warn "The API for Collection#group has changed. 'Finalize' is now the fifth parameter, " +
427
+ "since it's no longer necessary to specify whether #group is run as a command. " +
428
+ "See http://api.mongodb.org/ruby/current/Mongo/Collection.html#group-instance_method for details."
429
+ end
430
+
431
+ reduce = Code.new(reduce) unless reduce.is_a?(Code)
432
+
433
+ group_command = {
434
+ "group" => {
435
+ "ns" => @name,
436
+ "$reduce" => reduce,
437
+ "cond" => condition,
438
+ "initial" => initial
439
+ }
440
+ }
441
+
442
+ unless key.nil?
443
+ if key.is_a? Array
444
+ key_type = "key"
445
+ key_value = {}
446
+ key.each { |k| key_value[k] = 1 }
447
+ else
448
+ key_type = "$keyf"
449
+ key_value = key.is_a?(Code) ? key : Code.new(key)
450
+ end
451
+
452
+ group_command["group"][key_type] = key_value
453
+ end
454
+
455
+ # only add finalize if specified
456
+ # check to see if users have sent the finalizer as the last argument.
457
+ finalize = deprecated if deprecated.is_a?(String) || deprecated.is_a?(Code)
458
+ finalize = Code.new(finalize) if finalize.is_a?(String)
459
+ if finalize.is_a?(Code)
460
+ group_command['group']['finalize'] = finalize
461
+ end
462
+
463
+ result = @db.command group_command
464
+
465
+ if result["ok"] == 1
466
+ result["retval"]
467
+ else
468
+ raise OperationFailure, "group command failed: #{result['errmsg']}"
469
+ end
470
+ end
471
+
472
+ # Return a list of distinct values for +key+ across all
473
+ # documents in the collection. The key may use dot notation
474
+ # to reach into an embedded object.
475
+ #
476
+ # @param [String, Symbol, OrderedHash] key or hash to group by.
477
+ # @param [Hash] query a selector for limiting the result set over which to group.
478
+ #
479
+ # @example Saving zip codes and ages and returning distinct results.
480
+ # @collection.save({:zip => 10010, :name => {:age => 27}})
481
+ # @collection.save({:zip => 94108, :name => {:age => 24}})
482
+ # @collection.save({:zip => 10010, :name => {:age => 27}})
483
+ # @collection.save({:zip => 99701, :name => {:age => 24}})
484
+ # @collection.save({:zip => 94108, :name => {:age => 27}})
485
+ #
486
+ # @collection.distinct(:zip)
487
+ # [10010, 94108, 99701]
488
+ # @collection.distinct("name.age")
489
+ # [27, 24]
490
+ #
491
+ # # You may also pass a document selector as the second parameter
492
+ # # to limit the documents over which distinct is run:
493
+ # @collection.distinct("name.age", {"name.age" => {"$gt" => 24}})
494
+ # [27]
495
+ #
496
+ # @return [Array] an array of distinct values.
497
+ def distinct(key, query=nil)
498
+ raise MongoArgumentError unless [String, Symbol].include?(key.class)
499
+ command = OrderedHash.new
500
+ command[:distinct] = @name
501
+ command[:key] = key.to_s
502
+ command[:query] = query
503
+
504
+ @db.command(command)["values"]
505
+ end
506
+
507
+ # Rename this collection.
508
+ #
509
+ # Note: If operating in auth mode, the client must be authorized as an admin to
510
+ # perform this operation.
511
+ #
512
+ # @param [String] new_name the new name for this collection
513
+ #
514
+ # @raise [InvalidName] if +new_name+ is an invalid collection name.
515
+ def rename(new_name)
516
+ case new_name
517
+ when Symbol, String
518
+ else
519
+ raise TypeError, "new_name must be a string or symbol"
520
+ end
521
+
522
+ new_name = new_name.to_s
523
+
524
+ if new_name.empty? or new_name.include? ".."
525
+ raise InvalidName, "collection names cannot be empty"
526
+ end
527
+ if new_name.include? "$"
528
+ raise InvalidName, "collection names must not contain '$'"
529
+ end
530
+ if new_name.match(/^\./) or new_name.match(/\.$/)
531
+ raise InvalidName, "collection names must not start or end with '.'"
532
+ end
533
+
534
+ @db.rename_collection(@name, new_name)
535
+ end
536
+
537
+ # Get information on the indexes for this collection.
538
+ #
539
+ # @return [Hash] a hash where the keys are index names.
540
+ #
541
+ # @core indexes
542
+ def index_information
543
+ @db.index_information(@name)
544
+ end
545
+
546
+ # Return a hash containing options that apply to this collection.
547
+ # For all possible keys and values, see DB#create_collection.
548
+ #
549
+ # @return [Hash] options that apply to this collection.
550
+ def options
551
+ @db.collections_info(@name).next_document['options']
552
+ end
553
+
554
+ # Get the number of documents in this collection.
555
+ #
556
+ # @return [Integer]
557
+ def count
558
+ find().count()
559
+ end
560
+
561
+ alias :size :count
562
+
563
+ protected
564
+
565
+ def normalize_hint_fields(hint)
566
+ case hint
567
+ when String
568
+ {hint => 1}
569
+ when Hash
570
+ hint
571
+ when nil
572
+ nil
573
+ else
574
+ h = OrderedHash.new
575
+ hint.to_a.each { |k| h[k] = 1 }
576
+ h
577
+ end
578
+ end
579
+
580
+ private
581
+
582
+ # Sends a Mongo::Constants::OP_INSERT message to the database.
583
+ # Takes an array of +documents+, an optional +collection_name+, and a
584
+ # +check_keys+ setting.
585
+ def insert_documents(documents, collection_name=@name, check_keys=true, safe=false)
586
+ # Initial byte is 0.
587
+ message = ByteBuffer.new([0, 0, 0, 0])
588
+ BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{collection_name}")
589
+ documents.each { |doc| message.put_array(BSON.serialize(doc, check_keys, true).to_a) }
590
+ if safe
591
+ @connection.send_message_with_safe_check(Mongo::Constants::OP_INSERT, message, @db.name,
592
+ "db.#{collection_name}.insert(#{documents.inspect})")
593
+ else
594
+ @connection.send_message(Mongo::Constants::OP_INSERT, message,
595
+ "db.#{collection_name}.insert(#{documents.inspect})")
596
+ end
597
+ documents.collect { |o| o[:_id] || o['_id'] }
598
+ end
599
+
600
+ def generate_index_names(spec)
601
+ indexes = []
602
+ spec.each_pair do |field, direction|
603
+ indexes.push("#{field}_#{direction}")
604
+ end
605
+ indexes.join("_")
606
+ end
607
+ end
608
+
609
+ end