mongo-lyon 1.2.4
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 +190 -0
- data/README.md +344 -0
- data/Rakefile +202 -0
- data/bin/mongo_console +34 -0
- data/docs/1.0_UPGRADE.md +21 -0
- data/docs/CREDITS.md +123 -0
- data/docs/FAQ.md +116 -0
- data/docs/GridFS.md +158 -0
- data/docs/HISTORY.md +225 -0
- data/docs/REPLICA_SETS.md +72 -0
- data/docs/TUTORIAL.md +247 -0
- data/docs/WRITE_CONCERN.md +28 -0
- data/lib/mongo.rb +77 -0
- data/lib/mongo/collection.rb +872 -0
- data/lib/mongo/connection.rb +875 -0
- data/lib/mongo/cursor.rb +449 -0
- data/lib/mongo/db.rb +607 -0
- data/lib/mongo/exceptions.rb +68 -0
- data/lib/mongo/gridfs/grid.rb +106 -0
- data/lib/mongo/gridfs/grid_ext.rb +57 -0
- data/lib/mongo/gridfs/grid_file_system.rb +145 -0
- data/lib/mongo/gridfs/grid_io.rb +394 -0
- data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
- data/lib/mongo/repl_set_connection.rb +342 -0
- data/lib/mongo/util/conversions.rb +89 -0
- data/lib/mongo/util/core_ext.rb +60 -0
- data/lib/mongo/util/pool.rb +185 -0
- data/lib/mongo/util/server_version.rb +71 -0
- data/lib/mongo/util/support.rb +82 -0
- data/lib/mongo/util/uri_parser.rb +181 -0
- data/lib/mongo/version.rb +3 -0
- data/mongo.gemspec +34 -0
- data/test/auxillary/1.4_features.rb +166 -0
- data/test/auxillary/authentication_test.rb +68 -0
- data/test/auxillary/autoreconnect_test.rb +41 -0
- data/test/auxillary/repl_set_auth_test.rb +58 -0
- data/test/auxillary/slave_connection_test.rb +36 -0
- data/test/auxillary/threaded_authentication_test.rb +101 -0
- data/test/bson/binary_test.rb +15 -0
- data/test/bson/bson_test.rb +614 -0
- data/test/bson/byte_buffer_test.rb +190 -0
- data/test/bson/hash_with_indifferent_access_test.rb +38 -0
- data/test/bson/json_test.rb +17 -0
- data/test/bson/object_id_test.rb +154 -0
- data/test/bson/ordered_hash_test.rb +197 -0
- data/test/collection_test.rb +893 -0
- data/test/connection_test.rb +303 -0
- data/test/conversions_test.rb +120 -0
- data/test/cursor_fail_test.rb +75 -0
- data/test/cursor_message_test.rb +43 -0
- data/test/cursor_test.rb +457 -0
- data/test/db_api_test.rb +715 -0
- data/test/db_connection_test.rb +15 -0
- data/test/db_test.rb +287 -0
- data/test/grid_file_system_test.rb +244 -0
- data/test/grid_io_test.rb +120 -0
- data/test/grid_test.rb +200 -0
- data/test/load/thin/load.rb +24 -0
- data/test/load/unicorn/load.rb +23 -0
- data/test/replica_sets/connect_test.rb +86 -0
- data/test/replica_sets/connection_string_test.rb +32 -0
- data/test/replica_sets/count_test.rb +35 -0
- data/test/replica_sets/insert_test.rb +53 -0
- data/test/replica_sets/pooled_insert_test.rb +55 -0
- data/test/replica_sets/query_secondaries.rb +96 -0
- data/test/replica_sets/query_test.rb +51 -0
- data/test/replica_sets/replication_ack_test.rb +66 -0
- data/test/replica_sets/rs_test_helper.rb +27 -0
- data/test/safe_test.rb +68 -0
- data/test/support/hash_with_indifferent_access.rb +199 -0
- data/test/support/keys.rb +45 -0
- data/test/support_test.rb +19 -0
- data/test/test_helper.rb +83 -0
- data/test/threading/threading_with_large_pool_test.rb +90 -0
- data/test/threading_test.rb +87 -0
- data/test/tools/auth_repl_set_manager.rb +14 -0
- data/test/tools/repl_set_manager.rb +266 -0
- data/test/unit/collection_test.rb +130 -0
- data/test/unit/connection_test.rb +98 -0
- data/test/unit/cursor_test.rb +99 -0
- data/test/unit/db_test.rb +96 -0
- data/test/unit/grid_test.rb +49 -0
- data/test/unit/pool_test.rb +9 -0
- data/test/unit/repl_set_connection_test.rb +72 -0
- data/test/unit/safe_test.rb +125 -0
- data/test/uri_test.rb +91 -0
- metadata +202 -0
data/lib/mongo/cursor.rb
ADDED
@@ -0,0 +1,449 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright (C) 2008-2011 10gen Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module Mongo
|
18
|
+
|
19
|
+
# A cursor over query results. Returned objects are hashes.
|
20
|
+
class Cursor
|
21
|
+
include Mongo::Conversions
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
attr_reader :collection, :selector, :fields,
|
25
|
+
:order, :hint, :snapshot, :timeout,
|
26
|
+
:full_collection_name
|
27
|
+
|
28
|
+
# Create a new cursor.
|
29
|
+
#
|
30
|
+
# Note: cursors are created when executing queries using [Collection#find] and other
|
31
|
+
# similar methods. Application developers shouldn't have to create cursors manually.
|
32
|
+
#
|
33
|
+
# @return [Cursor]
|
34
|
+
#
|
35
|
+
# @core cursors constructor_details
|
36
|
+
def initialize(collection, opts={})
|
37
|
+
@db = collection.db
|
38
|
+
@collection = collection
|
39
|
+
@connection = @db.connection
|
40
|
+
@logger = @connection.logger
|
41
|
+
|
42
|
+
@selector = opts[:selector] || {}
|
43
|
+
@fields = convert_fields_for_query(opts[:fields])
|
44
|
+
@skip = opts[:skip] || 0
|
45
|
+
@limit = opts[:limit] || 0
|
46
|
+
@order = opts[:order]
|
47
|
+
@hint = opts[:hint]
|
48
|
+
@snapshot = opts[:snapshot]
|
49
|
+
@timeout = opts.fetch(:timeout, true)
|
50
|
+
@explain = opts[:explain]
|
51
|
+
@socket = opts[:socket]
|
52
|
+
@tailable = opts[:tailable] || false
|
53
|
+
@closed = false
|
54
|
+
@query_run = false
|
55
|
+
batch_size(opts[:batch_size] || 0)
|
56
|
+
|
57
|
+
@full_collection_name = "#{@collection.db.name}.#{@collection.name}"
|
58
|
+
@cache = []
|
59
|
+
@returned = 0
|
60
|
+
|
61
|
+
if @collection.name =~ /^\$cmd/ || @collection.name =~ /^system/
|
62
|
+
@command = true
|
63
|
+
else
|
64
|
+
@command = false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get the next document specified the cursor options.
|
69
|
+
#
|
70
|
+
# @return [Hash, Nil] the next document or Nil if no documents remain.
|
71
|
+
def next_document
|
72
|
+
refresh if @cache.length == 0
|
73
|
+
doc = @cache.shift
|
74
|
+
|
75
|
+
if doc && doc['$err']
|
76
|
+
err = doc['$err']
|
77
|
+
|
78
|
+
# If the server has stopped being the master (e.g., it's one of a
|
79
|
+
# pair but it has died or something like that) then we close that
|
80
|
+
# connection. The next request will re-open on master server.
|
81
|
+
if err == "not master"
|
82
|
+
@connection.close
|
83
|
+
raise ConnectionFailure, err
|
84
|
+
end
|
85
|
+
|
86
|
+
raise OperationFailure, err
|
87
|
+
end
|
88
|
+
|
89
|
+
doc
|
90
|
+
end
|
91
|
+
alias :next :next_document
|
92
|
+
|
93
|
+
# Reset this cursor on the server. Cursor options, such as the
|
94
|
+
# query string and the values for skip and limit, are preserved.
|
95
|
+
def rewind!
|
96
|
+
close
|
97
|
+
@cache.clear
|
98
|
+
@cursor_id = nil
|
99
|
+
@closed = false
|
100
|
+
@query_run = false
|
101
|
+
@n_received = nil
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
# Determine whether this cursor has any remaining results.
|
106
|
+
#
|
107
|
+
# @return [Boolean]
|
108
|
+
def has_next?
|
109
|
+
num_remaining > 0
|
110
|
+
end
|
111
|
+
|
112
|
+
# Get the size of the result set for this query.
|
113
|
+
#
|
114
|
+
# @param [Boolean] whether of not to take notice of skip and limit
|
115
|
+
#
|
116
|
+
# @return [Integer] the number of objects in the result set for this query.
|
117
|
+
#
|
118
|
+
# @raise [OperationFailure] on a database error.
|
119
|
+
def count(skip_and_limit = false)
|
120
|
+
command = BSON::OrderedHash["count", @collection.name, "query", @selector]
|
121
|
+
|
122
|
+
if skip_and_limit
|
123
|
+
command.merge!(BSON::OrderedHash["limit", @limit]) if @limit != 0
|
124
|
+
command.merge!(BSON::OrderedHash["skip", @skip]) if @skip != 0
|
125
|
+
end
|
126
|
+
|
127
|
+
command.merge!(BSON::OrderedHash["fields", @fields])
|
128
|
+
|
129
|
+
response = @db.command(command)
|
130
|
+
return response['n'].to_i if Mongo::Support.ok?(response)
|
131
|
+
return 0 if response['errmsg'] == "ns missing"
|
132
|
+
raise OperationFailure, "Count failed: #{response['errmsg']}"
|
133
|
+
end
|
134
|
+
|
135
|
+
# Sort this cursor's results.
|
136
|
+
#
|
137
|
+
# This method overrides any sort order specified in the Collection#find
|
138
|
+
# method, and only the last sort applied has an effect.
|
139
|
+
#
|
140
|
+
# @param [Symbol, Array] key_or_list either 1) a key to sort by or 2)
|
141
|
+
# an array of [key, direction] pairs to sort by. Direction should
|
142
|
+
# be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
|
143
|
+
#
|
144
|
+
# @raise [InvalidOperation] if this cursor has already been used.
|
145
|
+
#
|
146
|
+
# @raise [InvalidSortValueError] if the specified order is invalid.
|
147
|
+
def sort(key_or_list, direction=nil)
|
148
|
+
check_modifiable
|
149
|
+
|
150
|
+
if !direction.nil?
|
151
|
+
order = [[key_or_list, direction]]
|
152
|
+
else
|
153
|
+
order = key_or_list
|
154
|
+
end
|
155
|
+
|
156
|
+
@order = order
|
157
|
+
self
|
158
|
+
end
|
159
|
+
|
160
|
+
# Limit the number of results to be returned by this cursor.
|
161
|
+
#
|
162
|
+
# This method overrides any limit specified in the Collection#find method,
|
163
|
+
# and only the last limit applied has an effect.
|
164
|
+
#
|
165
|
+
# @return [Integer] the current number_to_return if no parameter is given.
|
166
|
+
#
|
167
|
+
# @raise [InvalidOperation] if this cursor has already been used.
|
168
|
+
#
|
169
|
+
# @core limit limit-instance_method
|
170
|
+
def limit(number_to_return=nil)
|
171
|
+
return @limit unless number_to_return
|
172
|
+
check_modifiable
|
173
|
+
|
174
|
+
@limit = number_to_return
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
178
|
+
# Skips the first +number_to_skip+ results of this cursor.
|
179
|
+
# Returns the current number_to_skip if no parameter is given.
|
180
|
+
#
|
181
|
+
# This method overrides any skip specified in the Collection#find method,
|
182
|
+
# and only the last skip applied has an effect.
|
183
|
+
#
|
184
|
+
# @return [Integer]
|
185
|
+
#
|
186
|
+
# @raise [InvalidOperation] if this cursor has already been used.
|
187
|
+
def skip(number_to_skip=nil)
|
188
|
+
return @skip unless number_to_skip
|
189
|
+
check_modifiable
|
190
|
+
|
191
|
+
@skip = number_to_skip
|
192
|
+
self
|
193
|
+
end
|
194
|
+
|
195
|
+
# Set the batch size for server responses.
|
196
|
+
#
|
197
|
+
# Note that the batch size will take effect only on queries
|
198
|
+
# where the number to be returned is greater than 100.
|
199
|
+
#
|
200
|
+
# @param [Integer] size either 0 or some integer greater than 1. If 0,
|
201
|
+
# the server will determine the batch size.
|
202
|
+
#
|
203
|
+
# @return [Cursor]
|
204
|
+
def batch_size(size=0)
|
205
|
+
check_modifiable
|
206
|
+
if size < 0 || size == 1
|
207
|
+
raise ArgumentError, "Invalid value for batch_size #{size}; must be 0 or > 1."
|
208
|
+
else
|
209
|
+
@batch_size = size > @limit ? @limit : size
|
210
|
+
end
|
211
|
+
|
212
|
+
self
|
213
|
+
end
|
214
|
+
|
215
|
+
# Iterate over each document in this cursor, yielding it to the given
|
216
|
+
# block.
|
217
|
+
#
|
218
|
+
# Iterating over an entire cursor will close it.
|
219
|
+
#
|
220
|
+
# @yield passes each document to a block for processing.
|
221
|
+
#
|
222
|
+
# @example if 'comments' represents a collection of comments:
|
223
|
+
# comments.find.each do |doc|
|
224
|
+
# puts doc['user']
|
225
|
+
# end
|
226
|
+
def each
|
227
|
+
#num_returned = 0
|
228
|
+
#while has_next? && (@limit <= 0 || num_returned < @limit)
|
229
|
+
while doc = next_document
|
230
|
+
yield doc #next_document
|
231
|
+
#num_returned += 1
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Receive all the documents from this cursor as an array of hashes.
|
236
|
+
#
|
237
|
+
# Notes:
|
238
|
+
#
|
239
|
+
# If you've already started iterating over the cursor, the array returned
|
240
|
+
# by this method contains only the remaining documents. See Cursor#rewind! if you
|
241
|
+
# need to reset the cursor.
|
242
|
+
#
|
243
|
+
# Use of this method is discouraged - in most cases, it's much more
|
244
|
+
# efficient to retrieve documents as you need them by iterating over the cursor.
|
245
|
+
#
|
246
|
+
# @return [Array] an array of documents.
|
247
|
+
def to_a
|
248
|
+
super
|
249
|
+
end
|
250
|
+
|
251
|
+
# Get the explain plan for this cursor.
|
252
|
+
#
|
253
|
+
# @return [Hash] a document containing the explain plan for this cursor.
|
254
|
+
#
|
255
|
+
# @core explain explain-instance_method
|
256
|
+
def explain
|
257
|
+
c = Cursor.new(@collection, query_options_hash.merge(:limit => -@limit.abs, :explain => true))
|
258
|
+
explanation = c.next_document
|
259
|
+
c.close
|
260
|
+
|
261
|
+
explanation
|
262
|
+
end
|
263
|
+
|
264
|
+
# Close the cursor.
|
265
|
+
#
|
266
|
+
# Note: if a cursor is read until exhausted (read until Mongo::Constants::OP_QUERY or
|
267
|
+
# Mongo::Constants::OP_GETMORE returns zero for the cursor id), there is no need to
|
268
|
+
# close it manually.
|
269
|
+
#
|
270
|
+
# Note also: Collection#find takes an optional block argument which can be used to
|
271
|
+
# ensure that your cursors get closed.
|
272
|
+
#
|
273
|
+
# @return [True]
|
274
|
+
def close
|
275
|
+
if @cursor_id && @cursor_id != 0
|
276
|
+
message = BSON::ByteBuffer.new([0, 0, 0, 0])
|
277
|
+
message.put_int(1)
|
278
|
+
message.put_long(@cursor_id)
|
279
|
+
@logger.debug("MONGODB cursor.close #{@cursor_id}") if @logger
|
280
|
+
@connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, nil)
|
281
|
+
end
|
282
|
+
@cursor_id = 0
|
283
|
+
@closed = true
|
284
|
+
end
|
285
|
+
|
286
|
+
# Is this cursor closed?
|
287
|
+
#
|
288
|
+
# @return [Boolean]
|
289
|
+
def closed?; @closed; end
|
290
|
+
|
291
|
+
# Returns an integer indicating which query options have been selected.
|
292
|
+
#
|
293
|
+
# @return [Integer]
|
294
|
+
#
|
295
|
+
# @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
|
296
|
+
# The MongoDB wire protocol.
|
297
|
+
def query_opts
|
298
|
+
opts = 0
|
299
|
+
opts |= Mongo::Constants::OP_QUERY_NO_CURSOR_TIMEOUT unless @timeout
|
300
|
+
opts |= Mongo::Constants::OP_QUERY_SLAVE_OK if @connection.slave_ok?
|
301
|
+
opts |= Mongo::Constants::OP_QUERY_TAILABLE if @tailable
|
302
|
+
opts
|
303
|
+
end
|
304
|
+
|
305
|
+
# Get the query options for this Cursor.
|
306
|
+
#
|
307
|
+
# @return [Hash]
|
308
|
+
def query_options_hash
|
309
|
+
{ :selector => @selector,
|
310
|
+
:fields => @fields,
|
311
|
+
:skip => @skip_num,
|
312
|
+
:limit => @limit_num,
|
313
|
+
:order => @order,
|
314
|
+
:hint => @hint,
|
315
|
+
:snapshot => @snapshot,
|
316
|
+
:timeout => @timeout }
|
317
|
+
end
|
318
|
+
|
319
|
+
# Clean output for inspect.
|
320
|
+
def inspect
|
321
|
+
"<Mongo::Cursor:0x#{object_id.to_s(16)} namespace='#{@db.name}.#{@collection.name}' " +
|
322
|
+
"@selector=#{@selector.inspect}>"
|
323
|
+
end
|
324
|
+
|
325
|
+
private
|
326
|
+
|
327
|
+
# Convert the +:fields+ parameter from a single field name or an array
|
328
|
+
# of fields names to a hash, with the field names for keys and '1' for each
|
329
|
+
# value.
|
330
|
+
def convert_fields_for_query(fields)
|
331
|
+
case fields
|
332
|
+
when String, Symbol
|
333
|
+
{fields => 1}
|
334
|
+
when Array
|
335
|
+
return nil if fields.length.zero?
|
336
|
+
fields.each_with_object({}) { |field, hash| hash[field] = 1 }
|
337
|
+
when Hash
|
338
|
+
return fields
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Return the number of documents remaining for this cursor.
|
343
|
+
def num_remaining
|
344
|
+
refresh if @cache.length == 0
|
345
|
+
@cache.length
|
346
|
+
end
|
347
|
+
|
348
|
+
def refresh
|
349
|
+
return if send_initial_query || @cursor_id.zero?
|
350
|
+
message = BSON::ByteBuffer.new([0, 0, 0, 0])
|
351
|
+
|
352
|
+
# DB name.
|
353
|
+
BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
|
354
|
+
|
355
|
+
# Number of results to return.
|
356
|
+
if @limit > 0
|
357
|
+
limit = @limit - @returned
|
358
|
+
if @batch_size > 0
|
359
|
+
limit = limit < @batch_size ? limit : @batch_size
|
360
|
+
end
|
361
|
+
message.put_int(limit)
|
362
|
+
else
|
363
|
+
message.put_int(@batch_size)
|
364
|
+
end
|
365
|
+
|
366
|
+
# Cursor id.
|
367
|
+
message.put_long(@cursor_id)
|
368
|
+
@logger.debug("MONGODB cursor.refresh() for cursor #{@cursor_id}") if @logger
|
369
|
+
results, @n_received, @cursor_id = @connection.receive_message(
|
370
|
+
Mongo::Constants::OP_GET_MORE, message, nil, @socket, @command)
|
371
|
+
@returned += @n_received
|
372
|
+
@cache += results
|
373
|
+
close_cursor_if_query_complete
|
374
|
+
end
|
375
|
+
|
376
|
+
# Run query the first time we request an object from the wire
|
377
|
+
# TODO: should we be calling instrument_payload even if logging
|
378
|
+
# is disabled?
|
379
|
+
def send_initial_query
|
380
|
+
if @query_run
|
381
|
+
false
|
382
|
+
else
|
383
|
+
message = construct_query_message
|
384
|
+
@connection.instrument(:find, instrument_payload) do
|
385
|
+
results, @n_received, @cursor_id = @connection.receive_message(
|
386
|
+
Mongo::Constants::OP_QUERY, message, nil, @socket, @command)
|
387
|
+
@returned += @n_received
|
388
|
+
@cache += results
|
389
|
+
@query_run = true
|
390
|
+
close_cursor_if_query_complete
|
391
|
+
end
|
392
|
+
true
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def construct_query_message
|
397
|
+
message = BSON::ByteBuffer.new
|
398
|
+
message.put_int(query_opts)
|
399
|
+
BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
|
400
|
+
message.put_int(@skip)
|
401
|
+
message.put_int(@limit)
|
402
|
+
spec = query_contains_special_fields? ? construct_query_spec : @selector
|
403
|
+
message.put_binary(BSON::BSON_CODER.serialize(spec, false).to_s)
|
404
|
+
message.put_binary(BSON::BSON_CODER.serialize(@fields, false).to_s) if @fields
|
405
|
+
message
|
406
|
+
end
|
407
|
+
|
408
|
+
def instrument_payload
|
409
|
+
log = { :database => @db.name, :collection => @collection.name, :selector => selector }
|
410
|
+
log[:fields] = @fields if @fields
|
411
|
+
log[:skip] = @skip if @skip && (@skip > 0)
|
412
|
+
log[:limit] = @limit if @limit && (@limit > 0)
|
413
|
+
log[:order] = @order if @order
|
414
|
+
log
|
415
|
+
end
|
416
|
+
|
417
|
+
def construct_query_spec
|
418
|
+
return @selector if @selector.has_key?('$query')
|
419
|
+
spec = BSON::OrderedHash.new
|
420
|
+
spec['$query'] = @selector
|
421
|
+
spec['$orderby'] = Mongo::Support.format_order_clause(@order) if @order
|
422
|
+
spec['$hint'] = @hint if @hint && @hint.length > 0
|
423
|
+
spec['$explain'] = true if @explain
|
424
|
+
spec['$snapshot'] = true if @snapshot
|
425
|
+
spec
|
426
|
+
end
|
427
|
+
|
428
|
+
# Returns true if the query contains order, explain, hint, or snapshot.
|
429
|
+
def query_contains_special_fields?
|
430
|
+
@order || @explain || @hint || @snapshot
|
431
|
+
end
|
432
|
+
|
433
|
+
def to_s
|
434
|
+
"DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)"
|
435
|
+
end
|
436
|
+
|
437
|
+
def close_cursor_if_query_complete
|
438
|
+
if @limit > 0 && @returned >= @limit
|
439
|
+
close
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def check_modifiable
|
444
|
+
if @query_run || @closed
|
445
|
+
raise InvalidOperation, "Cannot modify the query once it has been run or closed."
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
data/lib/mongo/db.rb
ADDED
@@ -0,0 +1,607 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# --
|
4
|
+
# Copyright (C) 2008-2011 10gen Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
# ++
|
18
|
+
|
19
|
+
require 'socket'
|
20
|
+
require 'timeout'
|
21
|
+
require 'thread'
|
22
|
+
|
23
|
+
module Mongo
|
24
|
+
|
25
|
+
# A MongoDB database.
|
26
|
+
class DB
|
27
|
+
|
28
|
+
SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
|
29
|
+
SYSTEM_INDEX_COLLECTION = "system.indexes"
|
30
|
+
SYSTEM_PROFILE_COLLECTION = "system.profile"
|
31
|
+
SYSTEM_USER_COLLECTION = "system.users"
|
32
|
+
SYSTEM_JS_COLLECTION = "system.js"
|
33
|
+
SYSTEM_COMMAND_COLLECTION = "$cmd"
|
34
|
+
|
35
|
+
# Counter for generating unique request ids.
|
36
|
+
@@current_request_id = 0
|
37
|
+
|
38
|
+
# Strict mode enforces collection existence checks. When +true+,
|
39
|
+
# asking for a collection that does not exist, or trying to create a
|
40
|
+
# collection that already exists, raises an error.
|
41
|
+
#
|
42
|
+
# Strict mode is disabled by default, but enabled (+true+) at any time.
|
43
|
+
attr_writer :strict
|
44
|
+
|
45
|
+
# Returns the value of the +strict+ flag.
|
46
|
+
def strict?; @strict; end
|
47
|
+
|
48
|
+
# The name of the database and the local safe option.
|
49
|
+
attr_reader :name, :safe
|
50
|
+
|
51
|
+
# The Mongo::Connection instance connecting to the MongoDB server.
|
52
|
+
attr_reader :connection
|
53
|
+
|
54
|
+
# The length of time that Collection.ensure_index should cache index calls
|
55
|
+
attr_accessor :cache_time
|
56
|
+
|
57
|
+
# Instances of DB are normally obtained by calling Mongo#db.
|
58
|
+
#
|
59
|
+
# @param [String] name the database name.
|
60
|
+
# @param [Mongo::Connection] connection a connection object pointing to MongoDB. Note
|
61
|
+
# that databases are usually instantiated via the Connection class. See the examples below.
|
62
|
+
#
|
63
|
+
# @option opts [Boolean] :strict (False) If true, collections must exist to be accessed and must
|
64
|
+
# not exist to be created. See DB#collection and DB#create_collection.
|
65
|
+
#
|
66
|
+
# @option opts [Object, #create_pk(doc)] :pk (Mongo::ObjectId) A primary key factory object,
|
67
|
+
# which should take a hash and return a hash which merges the original hash with any primary key
|
68
|
+
# fields the factory wishes to inject. (NOTE: if the object already has a primary key,
|
69
|
+
# the factory should not inject a new key).
|
70
|
+
#
|
71
|
+
# @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
|
72
|
+
# propogated to Collection objects instantiated off of this DB. If no
|
73
|
+
# value is provided, the default value set on this instance's Connection object will be used. This
|
74
|
+
# default can be overridden upon instantiation of any collection by explicity setting a :safe value
|
75
|
+
# on initialization
|
76
|
+
# @option opts [Integer] :cache_time (300) Set the time that all ensure_index calls should cache the command.
|
77
|
+
#
|
78
|
+
# @core databases constructor_details
|
79
|
+
def initialize(name, connection, opts={})
|
80
|
+
@name = Mongo::Support.validate_db_name(name)
|
81
|
+
@connection = connection
|
82
|
+
@strict = opts[:strict]
|
83
|
+
@pk_factory = opts[:pk]
|
84
|
+
@safe = opts.fetch(:safe, @connection.safe)
|
85
|
+
@cache_time = opts[:cache_time] || 300 #5 minutes.
|
86
|
+
end
|
87
|
+
|
88
|
+
# Authenticate with the given username and password. Note that mongod
|
89
|
+
# must be started with the --auth option for authentication to be enabled.
|
90
|
+
#
|
91
|
+
# @param [String] username
|
92
|
+
# @param [String] password
|
93
|
+
# @param [Boolean] save_auth
|
94
|
+
# Save this authentication to the connection object using Connection#add_auth. This
|
95
|
+
# will ensure that the authentication will be applied on database reconnect. Note
|
96
|
+
# that this value must be true when using connection pooling.
|
97
|
+
#
|
98
|
+
# @return [Boolean]
|
99
|
+
#
|
100
|
+
# @raise [AuthenticationError]
|
101
|
+
#
|
102
|
+
# @core authenticate authenticate-instance_method
|
103
|
+
def authenticate(username, password, save_auth=true)
|
104
|
+
if @connection.pool_size > 1
|
105
|
+
if !save_auth
|
106
|
+
raise MongoArgumentError, "If using connection pooling, :save_auth must be set to true."
|
107
|
+
end
|
108
|
+
@connection.authenticate_pools
|
109
|
+
end
|
110
|
+
|
111
|
+
issue_authentication(username, password, save_auth)
|
112
|
+
end
|
113
|
+
|
114
|
+
def issue_authentication(username, password, save_auth=true, opts={})
|
115
|
+
doc = command({:getnonce => 1}, :check_response => false, :socket => opts[:socket])
|
116
|
+
raise MongoDBError, "Error retrieving nonce: #{doc}" unless ok?(doc)
|
117
|
+
nonce = doc['nonce']
|
118
|
+
|
119
|
+
auth = BSON::OrderedHash.new
|
120
|
+
auth['authenticate'] = 1
|
121
|
+
auth['user'] = username
|
122
|
+
auth['nonce'] = nonce
|
123
|
+
auth['key'] = Mongo::Support.auth_key(username, password, nonce)
|
124
|
+
if ok?(self.command(auth, :check_response => false, :socket => opts[:socket]))
|
125
|
+
if save_auth
|
126
|
+
@connection.add_auth(@name, username, password)
|
127
|
+
end
|
128
|
+
true
|
129
|
+
else
|
130
|
+
raise(Mongo::AuthenticationError, "Failed to authenticate user '#{username}' on db '#{self.name}'")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Adds a stored Javascript function to the database which can executed
|
135
|
+
# server-side in map_reduce, db.eval and $where clauses.
|
136
|
+
#
|
137
|
+
# @param [String] function_name
|
138
|
+
# @param [String] code
|
139
|
+
#
|
140
|
+
# @return [String] the function name saved to the database
|
141
|
+
def add_stored_function(function_name, code)
|
142
|
+
self[SYSTEM_JS_COLLECTION].save(
|
143
|
+
{
|
144
|
+
"_id" => function_name,
|
145
|
+
:value => BSON::Code.new(code)
|
146
|
+
}
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Removes stored Javascript function from the database. Returns
|
151
|
+
# false if the function does not exist
|
152
|
+
#
|
153
|
+
# @param [String] function_name
|
154
|
+
#
|
155
|
+
# @return [Boolean]
|
156
|
+
def remove_stored_function(function_name)
|
157
|
+
if self[SYSTEM_JS_COLLECTION].find_one({"_id" => function_name})
|
158
|
+
self[SYSTEM_JS_COLLECTION].remove({"_id" => function_name}, :safe => true)
|
159
|
+
else
|
160
|
+
return false
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Adds a user to this database for use with authentication. If the user already
|
165
|
+
# exists in the system, the password will be updated.
|
166
|
+
#
|
167
|
+
# @param [String] username
|
168
|
+
# @param [String] password
|
169
|
+
#
|
170
|
+
# @return [Hash] an object representing the user.
|
171
|
+
def add_user(username, password)
|
172
|
+
users = self[SYSTEM_USER_COLLECTION]
|
173
|
+
user = users.find_one({:user => username}) || {:user => username}
|
174
|
+
user['pwd'] = Mongo::Support.hash_password(username, password)
|
175
|
+
users.save(user)
|
176
|
+
return user
|
177
|
+
end
|
178
|
+
|
179
|
+
# Remove the given user from this database. Returns false if the user
|
180
|
+
# doesn't exist in the system.
|
181
|
+
#
|
182
|
+
# @param [String] username
|
183
|
+
#
|
184
|
+
# @return [Boolean]
|
185
|
+
def remove_user(username)
|
186
|
+
if self[SYSTEM_USER_COLLECTION].find_one({:user => username})
|
187
|
+
self[SYSTEM_USER_COLLECTION].remove({:user => username}, :safe => true)
|
188
|
+
else
|
189
|
+
return false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Deauthorizes use for this database for this connection. Also removes
|
194
|
+
# any saved authentication in the connection class associated with this
|
195
|
+
# database.
|
196
|
+
#
|
197
|
+
# @raise [MongoDBError] if logging out fails.
|
198
|
+
#
|
199
|
+
# @return [Boolean]
|
200
|
+
def logout(opts={})
|
201
|
+
if @connection.pool_size > 1
|
202
|
+
@connection.logout_pools(@name)
|
203
|
+
end
|
204
|
+
|
205
|
+
issue_logout(opts)
|
206
|
+
end
|
207
|
+
|
208
|
+
def issue_logout(opts={})
|
209
|
+
doc = command({:logout => 1}, :socket => opts[:socket])
|
210
|
+
if ok?(doc)
|
211
|
+
@connection.remove_auth(@name)
|
212
|
+
true
|
213
|
+
else
|
214
|
+
raise MongoDBError, "error logging out: #{doc.inspect}"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Get an array of collection names in this database.
|
219
|
+
#
|
220
|
+
# @return [Array]
|
221
|
+
def collection_names
|
222
|
+
names = collections_info.collect { |doc| doc['name'] || '' }
|
223
|
+
names = names.delete_if {|name| name.index(@name).nil? || name.index('$')}
|
224
|
+
names.map {|name| name.sub(@name + '.', '')}
|
225
|
+
end
|
226
|
+
|
227
|
+
# Get an array of Collection instances, one for each collection in this database.
|
228
|
+
#
|
229
|
+
# @return [Array<Mongo::Collection>]
|
230
|
+
def collections
|
231
|
+
collection_names.map do |name|
|
232
|
+
Collection.new(name, self)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Get info on system namespaces (collections). This method returns
|
237
|
+
# a cursor which can be iterated over. For each collection, a hash
|
238
|
+
# will be yielded containing a 'name' string and, optionally, an 'options' hash.
|
239
|
+
#
|
240
|
+
# @param [String] coll_name return info for the specifed collection only.
|
241
|
+
#
|
242
|
+
# @return [Mongo::Cursor]
|
243
|
+
def collections_info(coll_name=nil)
|
244
|
+
selector = {}
|
245
|
+
selector[:name] = full_collection_name(coll_name) if coll_name
|
246
|
+
Cursor.new(Collection.new(SYSTEM_NAMESPACE_COLLECTION, self), :selector => selector)
|
247
|
+
end
|
248
|
+
|
249
|
+
# Create a collection.
|
250
|
+
#
|
251
|
+
# new collection. If +strict+ is true, will raise an error if
|
252
|
+
# collection +name+ already exists.
|
253
|
+
#
|
254
|
+
# @param [String] name the name of the new collection.
|
255
|
+
#
|
256
|
+
# @option opts [Boolean] :capped (False) created a capped collection.
|
257
|
+
#
|
258
|
+
# @option opts [Integer] :size (Nil) If +capped+ is +true+, specifies the maximum number of
|
259
|
+
# bytes for the capped collection. If +false+, specifies the number of bytes allocated
|
260
|
+
# for the initial extent of the collection.
|
261
|
+
#
|
262
|
+
# @option opts [Integer] :max (Nil) If +capped+ is +true+, indicates the maximum number of records
|
263
|
+
# in a capped collection.
|
264
|
+
#
|
265
|
+
# @raise [MongoDBError] raised under two conditions: either we're in +strict+ mode and the collection
|
266
|
+
# already exists or collection creation fails on the server.
|
267
|
+
#
|
268
|
+
# @return [Mongo::Collection]
|
269
|
+
def create_collection(name, opts={})
|
270
|
+
# Does the collection already exist?
|
271
|
+
if collection_names.include?(name)
|
272
|
+
if strict?
|
273
|
+
raise MongoDBError, "Collection #{name} already exists. Currently in strict mode."
|
274
|
+
else
|
275
|
+
return Collection.new(name, self)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Create a new collection.
|
280
|
+
oh = BSON::OrderedHash.new
|
281
|
+
oh[:create] = name
|
282
|
+
doc = command(oh.merge(opts || {}))
|
283
|
+
return Collection.new(name, self, :pk => @pk_factory) if ok?(doc)
|
284
|
+
raise MongoDBError, "Error creating collection: #{doc.inspect}"
|
285
|
+
end
|
286
|
+
|
287
|
+
# Get a collection by name.
|
288
|
+
#
|
289
|
+
# @param [String] name the collection name.
|
290
|
+
# @param [Hash] opts any valid options that can me passed to Collection#new.
|
291
|
+
#
|
292
|
+
# @raise [MongoDBError] if collection does not already exist and we're in +strict+ mode.
|
293
|
+
#
|
294
|
+
# @return [Mongo::Collection]
|
295
|
+
def collection(name, opts={})
|
296
|
+
if strict? && !collection_names.include?(name)
|
297
|
+
raise Mongo::MongoDBError, "Collection #{name} doesn't exist. Currently in strict mode."
|
298
|
+
else
|
299
|
+
opts[:safe] = opts.fetch(:safe, @safe)
|
300
|
+
opts.merge!(:pk => @pk_factory) unless opts[:pk]
|
301
|
+
Collection.new(name, self, opts)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
alias_method :[], :collection
|
305
|
+
|
306
|
+
# Drop a collection by +name+.
|
307
|
+
#
|
308
|
+
# @param [String] name
|
309
|
+
#
|
310
|
+
# @return [Boolean] +true+ on success or +false+ if the collection name doesn't exist.
|
311
|
+
def drop_collection(name)
|
312
|
+
return true unless collection_names.include?(name)
|
313
|
+
|
314
|
+
ok?(command(:drop => name))
|
315
|
+
end
|
316
|
+
|
317
|
+
# Run the getlasterror command with the specified replication options.
|
318
|
+
#
|
319
|
+
# @option opts [Boolean] :fsync (false)
|
320
|
+
# @option opts [Integer] :w (nil)
|
321
|
+
# @option opts [Integer] :wtimeout (nil)
|
322
|
+
#
|
323
|
+
# @return [Hash] the entire response to getlasterror.
|
324
|
+
#
|
325
|
+
# @raise [MongoDBError] if the operation fails.
|
326
|
+
def get_last_error(opts={})
|
327
|
+
cmd = BSON::OrderedHash.new
|
328
|
+
cmd[:getlasterror] = 1
|
329
|
+
cmd.merge!(opts)
|
330
|
+
doc = command(cmd, :check_response => false)
|
331
|
+
raise MongoDBError, "error retrieving last error: #{doc.inspect}" unless ok?(doc)
|
332
|
+
doc
|
333
|
+
end
|
334
|
+
|
335
|
+
# Return +true+ if an error was caused by the most recently executed
|
336
|
+
# database operation.
|
337
|
+
#
|
338
|
+
# @return [Boolean]
|
339
|
+
def error?
|
340
|
+
get_last_error['err'] != nil
|
341
|
+
end
|
342
|
+
|
343
|
+
# Get the most recent error to have occured on this database.
|
344
|
+
#
|
345
|
+
# This command only returns errors that have occured since the last call to
|
346
|
+
# DB#reset_error_history - returns +nil+ if there is no such error.
|
347
|
+
#
|
348
|
+
# @return [String, Nil] the text of the error or +nil+ if no error has occurred.
|
349
|
+
def previous_error
|
350
|
+
error = command(:getpreverror => 1)
|
351
|
+
if error["err"]
|
352
|
+
error
|
353
|
+
else
|
354
|
+
nil
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Reset the error history of this database
|
359
|
+
#
|
360
|
+
# Calls to DB#previous_error will only return errors that have occurred
|
361
|
+
# since the most recent call to this method.
|
362
|
+
#
|
363
|
+
# @return [Hash]
|
364
|
+
def reset_error_history
|
365
|
+
command(:reseterror => 1)
|
366
|
+
end
|
367
|
+
|
368
|
+
# Dereference a DBRef, returning the document it points to.
|
369
|
+
#
|
370
|
+
# @param [Mongo::DBRef] dbref
|
371
|
+
#
|
372
|
+
# @return [Hash] the document indicated by the db reference.
|
373
|
+
#
|
374
|
+
# @see http://www.mongodb.org/display/DOCS/DB+Ref MongoDB DBRef spec.
|
375
|
+
def dereference(dbref)
|
376
|
+
collection(dbref.namespace).find_one("_id" => dbref.object_id)
|
377
|
+
end
|
378
|
+
|
379
|
+
# Evaluate a JavaScript expression in MongoDB.
|
380
|
+
#
|
381
|
+
# @param [String, Code] code a JavaScript expression to evaluate server-side.
|
382
|
+
# @param [Integer, Hash] args any additional argument to be passed to the +code+ expression when
|
383
|
+
# it's run on the server.
|
384
|
+
#
|
385
|
+
# @return [String] the return value of the function.
|
386
|
+
def eval(code, *args)
|
387
|
+
if not code.is_a? BSON::Code
|
388
|
+
code = BSON::Code.new(code)
|
389
|
+
end
|
390
|
+
|
391
|
+
oh = BSON::OrderedHash.new
|
392
|
+
oh[:$eval] = code
|
393
|
+
oh[:args] = args
|
394
|
+
doc = command(oh)
|
395
|
+
doc['retval']
|
396
|
+
end
|
397
|
+
|
398
|
+
# Rename a collection.
|
399
|
+
#
|
400
|
+
# @param [String] from original collection name.
|
401
|
+
# @param [String] to new collection name.
|
402
|
+
#
|
403
|
+
# @return [True] returns +true+ on success.
|
404
|
+
#
|
405
|
+
# @raise MongoDBError if there's an error renaming the collection.
|
406
|
+
def rename_collection(from, to)
|
407
|
+
oh = BSON::OrderedHash.new
|
408
|
+
oh[:renameCollection] = "#{@name}.#{from}"
|
409
|
+
oh[:to] = "#{@name}.#{to}"
|
410
|
+
doc = DB.new('admin', @connection).command(oh, :check_response => false)
|
411
|
+
ok?(doc) || raise(MongoDBError, "Error renaming collection: #{doc.inspect}")
|
412
|
+
end
|
413
|
+
|
414
|
+
# Drop an index from a given collection. Normally called from
|
415
|
+
# Collection#drop_index or Collection#drop_indexes.
|
416
|
+
#
|
417
|
+
# @param [String] collection_name
|
418
|
+
# @param [String] index_name
|
419
|
+
#
|
420
|
+
# @return [True] returns +true+ on success.
|
421
|
+
#
|
422
|
+
# @raise MongoDBError if there's an error renaming the collection.
|
423
|
+
def drop_index(collection_name, index_name)
|
424
|
+
oh = BSON::OrderedHash.new
|
425
|
+
oh[:deleteIndexes] = collection_name
|
426
|
+
oh[:index] = index_name.to_s
|
427
|
+
doc = command(oh, :check_response => false)
|
428
|
+
ok?(doc) || raise(MongoDBError, "Error with drop_index command: #{doc.inspect}")
|
429
|
+
end
|
430
|
+
|
431
|
+
# Get information on the indexes for the given collection.
|
432
|
+
# Normally called by Collection#index_information.
|
433
|
+
#
|
434
|
+
# @param [String] collection_name
|
435
|
+
#
|
436
|
+
# @return [Hash] keys are index names and the values are lists of [key, direction] pairs
|
437
|
+
# defining the index.
|
438
|
+
def index_information(collection_name)
|
439
|
+
sel = {:ns => full_collection_name(collection_name)}
|
440
|
+
info = {}
|
441
|
+
Cursor.new(Collection.new(SYSTEM_INDEX_COLLECTION, self), :selector => sel).each do |index|
|
442
|
+
info[index['name']] = index
|
443
|
+
end
|
444
|
+
info
|
445
|
+
end
|
446
|
+
|
447
|
+
# Return stats on this database. Uses MongoDB's dbstats command.
|
448
|
+
#
|
449
|
+
# @return [Hash]
|
450
|
+
def stats
|
451
|
+
self.command({:dbstats => 1})
|
452
|
+
end
|
453
|
+
|
454
|
+
# Return +true+ if the supplied +doc+ contains an 'ok' field with the value 1.
|
455
|
+
#
|
456
|
+
# @param [Hash] doc
|
457
|
+
#
|
458
|
+
# @return [Boolean]
|
459
|
+
def ok?(doc)
|
460
|
+
Mongo::Support.ok?(doc)
|
461
|
+
end
|
462
|
+
|
463
|
+
# Send a command to the database.
|
464
|
+
#
|
465
|
+
# Note: DB commands must start with the "command" key. For this reason,
|
466
|
+
# any selector containing more than one key must be an OrderedHash.
|
467
|
+
#
|
468
|
+
# Note also that a command in MongoDB is just a kind of query
|
469
|
+
# that occurs on the system command collection ($cmd). Examine this method's implementation
|
470
|
+
# to see how it works.
|
471
|
+
#
|
472
|
+
# @param [OrderedHash, Hash] selector an OrderedHash, or a standard Hash with just one
|
473
|
+
# key, specifying the command to be performed. In Ruby 1.9, OrderedHash isn't necessary since
|
474
|
+
# hashes are ordered by default.
|
475
|
+
#
|
476
|
+
# @option opts [Boolean] :check_response (true) If +true+, raises an exception if the
|
477
|
+
# command fails.
|
478
|
+
# @option opts [Socket] :socket a socket to use for sending the command. This is mainly for internal use.
|
479
|
+
#
|
480
|
+
# @return [Hash]
|
481
|
+
#
|
482
|
+
# @core commands command_instance-method
|
483
|
+
def command(selector, opts={})
|
484
|
+
check_response = opts.fetch(:check_response, true)
|
485
|
+
socket = opts[:socket]
|
486
|
+
raise MongoArgumentError, "command must be given a selector" unless selector.is_a?(Hash) && !selector.empty?
|
487
|
+
if selector.keys.length > 1 && RUBY_VERSION < '1.9' && selector.class != BSON::OrderedHash
|
488
|
+
raise MongoArgumentError, "DB#command requires an OrderedHash when hash contains multiple keys"
|
489
|
+
end
|
490
|
+
|
491
|
+
begin
|
492
|
+
result = Cursor.new(system_command_collection,
|
493
|
+
:limit => -1, :selector => selector, :socket => socket).next_document
|
494
|
+
rescue OperationFailure => ex
|
495
|
+
raise OperationFailure, "Database command '#{selector.keys.first}' failed: #{ex.message}"
|
496
|
+
end
|
497
|
+
|
498
|
+
if result.nil?
|
499
|
+
raise OperationFailure, "Database command '#{selector.keys.first}' failed: returned null."
|
500
|
+
elsif (check_response && !ok?(result))
|
501
|
+
raise OperationFailure, "Database command '#{selector.keys.first}' failed: #{result.inspect}"
|
502
|
+
else
|
503
|
+
result
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
# A shortcut returning db plus dot plus collection name.
|
508
|
+
#
|
509
|
+
# @param [String] collection_name
|
510
|
+
#
|
511
|
+
# @return [String]
|
512
|
+
def full_collection_name(collection_name)
|
513
|
+
"#{@name}.#{collection_name}"
|
514
|
+
end
|
515
|
+
|
516
|
+
# The primary key factory object (or +nil+).
|
517
|
+
#
|
518
|
+
# @return [Object, Nil]
|
519
|
+
def pk_factory
|
520
|
+
@pk_factory
|
521
|
+
end
|
522
|
+
|
523
|
+
# Specify a primary key factory if not already set.
|
524
|
+
#
|
525
|
+
# @raise [MongoArgumentError] if the primary key factory has already been set.
|
526
|
+
def pk_factory=(pk_factory)
|
527
|
+
if @pk_factory
|
528
|
+
raise MongoArgumentError, "Cannot change primary key factory once it's been set"
|
529
|
+
end
|
530
|
+
|
531
|
+
@pk_factory = pk_factory
|
532
|
+
end
|
533
|
+
|
534
|
+
# Return the current database profiling level. If profiling is enabled, you can
|
535
|
+
# get the results using DB#profiling_info.
|
536
|
+
#
|
537
|
+
# @return [Symbol] :off, :slow_only, or :all
|
538
|
+
#
|
539
|
+
# @core profiling profiling_level-instance_method
|
540
|
+
def profiling_level
|
541
|
+
oh = BSON::OrderedHash.new
|
542
|
+
oh[:profile] = -1
|
543
|
+
doc = command(oh, :check_response => false)
|
544
|
+
raise "Error with profile command: #{doc.inspect}" unless ok?(doc) && doc['was'].kind_of?(Numeric)
|
545
|
+
case doc['was'].to_i
|
546
|
+
when 0
|
547
|
+
:off
|
548
|
+
when 1
|
549
|
+
:slow_only
|
550
|
+
when 2
|
551
|
+
:all
|
552
|
+
else
|
553
|
+
raise "Error: illegal profiling level value #{doc['was']}"
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
# Set this database's profiling level. If profiling is enabled, you can
|
558
|
+
# get the results using DB#profiling_info.
|
559
|
+
#
|
560
|
+
# @param [Symbol] level acceptable options are +:off+, +:slow_only+, or +:all+.
|
561
|
+
def profiling_level=(level)
|
562
|
+
oh = BSON::OrderedHash.new
|
563
|
+
oh[:profile] = case level
|
564
|
+
when :off
|
565
|
+
0
|
566
|
+
when :slow_only
|
567
|
+
1
|
568
|
+
when :all
|
569
|
+
2
|
570
|
+
else
|
571
|
+
raise "Error: illegal profiling level value #{level}"
|
572
|
+
end
|
573
|
+
doc = command(oh, :check_response => false)
|
574
|
+
ok?(doc) || raise(MongoDBError, "Error with profile command: #{doc.inspect}")
|
575
|
+
end
|
576
|
+
|
577
|
+
# Get the current profiling information.
|
578
|
+
#
|
579
|
+
# @return [Array] a list of documents containing profiling information.
|
580
|
+
def profiling_info
|
581
|
+
Cursor.new(Collection.new(SYSTEM_PROFILE_COLLECTION, self), :selector => {}).to_a
|
582
|
+
end
|
583
|
+
|
584
|
+
# Validate a named collection.
|
585
|
+
#
|
586
|
+
# @param [String] name the collection name.
|
587
|
+
#
|
588
|
+
# @return [Hash] validation information.
|
589
|
+
#
|
590
|
+
# @raise [MongoDBError] if the command fails or there's a problem with the validation
|
591
|
+
# data, or if the collection is invalid.
|
592
|
+
def validate_collection(name)
|
593
|
+
doc = command({:validate => name}, :check_response => false)
|
594
|
+
raise MongoDBError, "Error with validate command: #{doc.inspect}" unless ok?(doc)
|
595
|
+
result = doc['result']
|
596
|
+
raise MongoDBError, "Error with validation data: #{doc.inspect}" unless result.kind_of?(String)
|
597
|
+
raise MongoDBError, "Error: invalid collection #{name}: #{doc.inspect}" if result =~ /\b(exception|corrupt)\b/i
|
598
|
+
doc
|
599
|
+
end
|
600
|
+
|
601
|
+
private
|
602
|
+
|
603
|
+
def system_command_collection
|
604
|
+
Collection.new(SYSTEM_COMMAND_COLLECTION, self)
|
605
|
+
end
|
606
|
+
end
|
607
|
+
end
|