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.
- data/LICENSE.txt +202 -0
- data/README.rdoc +339 -0
- data/Rakefile +138 -0
- data/bin/bson_benchmark.rb +59 -0
- data/bin/fail_if_no_c.rb +11 -0
- data/examples/admin.rb +42 -0
- data/examples/capped.rb +22 -0
- data/examples/cursor.rb +48 -0
- data/examples/gridfs.rb +88 -0
- data/examples/index_test.rb +126 -0
- data/examples/info.rb +31 -0
- data/examples/queries.rb +70 -0
- data/examples/simple.rb +24 -0
- data/examples/strict.rb +35 -0
- data/examples/types.rb +36 -0
- data/lib/mongo/collection.rb +609 -0
- data/lib/mongo/connection.rb +672 -0
- data/lib/mongo/cursor.rb +403 -0
- data/lib/mongo/db.rb +555 -0
- data/lib/mongo/exceptions.rb +66 -0
- data/lib/mongo/gridfs/chunk.rb +91 -0
- data/lib/mongo/gridfs/grid.rb +79 -0
- data/lib/mongo/gridfs/grid_file_system.rb +101 -0
- data/lib/mongo/gridfs/grid_io.rb +338 -0
- data/lib/mongo/gridfs/grid_store.rb +580 -0
- data/lib/mongo/gridfs.rb +25 -0
- data/lib/mongo/types/binary.rb +52 -0
- data/lib/mongo/types/code.rb +36 -0
- data/lib/mongo/types/dbref.rb +40 -0
- data/lib/mongo/types/min_max_keys.rb +58 -0
- data/lib/mongo/types/objectid.rb +180 -0
- data/lib/mongo/types/regexp_of_holding.rb +45 -0
- data/lib/mongo/util/bson_c.rb +18 -0
- data/lib/mongo/util/bson_ruby.rb +606 -0
- data/lib/mongo/util/byte_buffer.rb +222 -0
- data/lib/mongo/util/conversions.rb +87 -0
- data/lib/mongo/util/ordered_hash.rb +140 -0
- data/lib/mongo/util/server_version.rb +69 -0
- data/lib/mongo/util/support.rb +26 -0
- data/lib/mongo.rb +63 -0
- data/mongo-ruby-driver.gemspec +28 -0
- data/test/auxillary/autoreconnect_test.rb +42 -0
- data/test/binary_test.rb +15 -0
- data/test/bson_test.rb +427 -0
- data/test/byte_buffer_test.rb +81 -0
- data/test/chunk_test.rb +82 -0
- data/test/collection_test.rb +515 -0
- data/test/connection_test.rb +160 -0
- data/test/conversions_test.rb +120 -0
- data/test/cursor_test.rb +379 -0
- data/test/db_api_test.rb +780 -0
- data/test/db_connection_test.rb +16 -0
- data/test/db_test.rb +272 -0
- data/test/grid_file_system_test.rb +210 -0
- data/test/grid_io_test.rb +78 -0
- data/test/grid_store_test.rb +334 -0
- data/test/grid_test.rb +87 -0
- data/test/objectid_test.rb +125 -0
- data/test/ordered_hash_test.rb +172 -0
- data/test/replica/count_test.rb +34 -0
- data/test/replica/insert_test.rb +50 -0
- data/test/replica/pooled_insert_test.rb +54 -0
- data/test/replica/query_test.rb +39 -0
- data/test/slave_connection_test.rb +36 -0
- data/test/test_helper.rb +42 -0
- data/test/threading/test_threading_large_pool.rb +90 -0
- data/test/threading_test.rb +87 -0
- data/test/unit/collection_test.rb +61 -0
- data/test/unit/connection_test.rb +117 -0
- data/test/unit/cursor_test.rb +93 -0
- data/test/unit/db_test.rb +98 -0
- 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
|