kbaum-mongo 0.18.3p

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 (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