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
data/lib/mongo/cursor.rb
ADDED
@@ -0,0 +1,403 @@
|
|
1
|
+
# Copyright (C) 2008-2010 10gen Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module Mongo
|
16
|
+
|
17
|
+
# A cursor over query results. Returned objects are hashes.
|
18
|
+
class Cursor
|
19
|
+
include Mongo::Conversions
|
20
|
+
include Enumerable
|
21
|
+
|
22
|
+
attr_reader :collection, :selector, :admin, :fields,
|
23
|
+
:order, :hint, :snapshot, :timeout,
|
24
|
+
:full_collection_name
|
25
|
+
|
26
|
+
# Create a new cursor.
|
27
|
+
#
|
28
|
+
# Note: cursors are created when executing queries using [Collection#find] and other
|
29
|
+
# similar methods. Application developers shouldn't have to create cursors manually.
|
30
|
+
#
|
31
|
+
# @return [Cursor]
|
32
|
+
#
|
33
|
+
# @core cursors constructor_details
|
34
|
+
def initialize(collection, options={})
|
35
|
+
@db = collection.db
|
36
|
+
@collection = collection
|
37
|
+
@connection = @db.connection
|
38
|
+
|
39
|
+
@selector = convert_selector_for_query(options[:selector])
|
40
|
+
@fields = convert_fields_for_query(options[:fields])
|
41
|
+
@admin = options[:admin] || false
|
42
|
+
@skip = options[:skip] || 0
|
43
|
+
@limit = options[:limit] || 0
|
44
|
+
@order = options[:order]
|
45
|
+
@hint = options[:hint]
|
46
|
+
@snapshot = options[:snapshot]
|
47
|
+
@timeout = options[:timeout] || false
|
48
|
+
@explain = options[:explain]
|
49
|
+
@socket = options[:socket]
|
50
|
+
|
51
|
+
@full_collection_name = "#{@collection.db.name}.#{@collection.name}"
|
52
|
+
@cache = []
|
53
|
+
@closed = false
|
54
|
+
@query_run = false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get the next document specified the cursor options.
|
58
|
+
#
|
59
|
+
# @return [Hash, Nil] the next document or Nil if no documents remain.
|
60
|
+
def next_document
|
61
|
+
refill_via_get_more if num_remaining == 0
|
62
|
+
doc = @cache.shift
|
63
|
+
|
64
|
+
if doc && doc['$err']
|
65
|
+
err = doc['$err']
|
66
|
+
|
67
|
+
# If the server has stopped being the master (e.g., it's one of a
|
68
|
+
# pair but it has died or something like that) then we close that
|
69
|
+
# connection. The next request will re-open on master server.
|
70
|
+
if err == "not master"
|
71
|
+
raise ConnectionFailure, err
|
72
|
+
@connection.close
|
73
|
+
end
|
74
|
+
|
75
|
+
raise OperationFailure, err
|
76
|
+
end
|
77
|
+
|
78
|
+
doc
|
79
|
+
end
|
80
|
+
|
81
|
+
# Get the size of the result set for this query.
|
82
|
+
#
|
83
|
+
# @return [Integer] the number of objects in the result set for this query. Does
|
84
|
+
# not take limit and skip into account.
|
85
|
+
#
|
86
|
+
# @raise [OperationFailure] on a database error.
|
87
|
+
def count
|
88
|
+
command = OrderedHash["count", @collection.name,
|
89
|
+
"query", @selector,
|
90
|
+
"fields", @fields]
|
91
|
+
response = @db.command(command)
|
92
|
+
return response['n'].to_i if response['ok'] == 1
|
93
|
+
return 0 if response['errmsg'] == "ns missing"
|
94
|
+
raise OperationFailure, "Count failed: #{response['errmsg']}"
|
95
|
+
end
|
96
|
+
|
97
|
+
# Sort this cursor's results.
|
98
|
+
#
|
99
|
+
# This method overrides any sort order specified in the Collection#find
|
100
|
+
# method, and only the last sort applied has an effect.
|
101
|
+
#
|
102
|
+
# @param [Symbol, Array] key_or_list either 1) a key to sort by or 2)
|
103
|
+
# an array of [key, direction] pairs to sort by. Direction should
|
104
|
+
# be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
|
105
|
+
#
|
106
|
+
# @raise [InvalidOperation] if this cursor has already been used.
|
107
|
+
#
|
108
|
+
# @raise [InvalidSortValueError] if the specified order is invalid.
|
109
|
+
def sort(key_or_list, direction=nil)
|
110
|
+
check_modifiable
|
111
|
+
|
112
|
+
if !direction.nil?
|
113
|
+
order = [[key_or_list, direction]]
|
114
|
+
else
|
115
|
+
order = key_or_list
|
116
|
+
end
|
117
|
+
|
118
|
+
@order = order
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
# Limit the number of results to be returned by this cursor.
|
123
|
+
#
|
124
|
+
# This method overrides any limit specified in the Collection#find method,
|
125
|
+
# and only the last limit applied has an effect.
|
126
|
+
#
|
127
|
+
# @return [Integer] the current number_to_return if no parameter is given.
|
128
|
+
#
|
129
|
+
# @raise [InvalidOperation] if this cursor has already been used.
|
130
|
+
#
|
131
|
+
# @core limit limit-instance_method
|
132
|
+
def limit(number_to_return=nil)
|
133
|
+
return @limit unless number_to_return
|
134
|
+
check_modifiable
|
135
|
+
raise ArgumentError, "limit requires an integer" unless number_to_return.is_a? Integer
|
136
|
+
|
137
|
+
@limit = number_to_return
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
# Skips the first +number_to_skip+ results of this cursor.
|
142
|
+
# Returns the current number_to_skip if no parameter is given.
|
143
|
+
#
|
144
|
+
# This method overrides any skip specified in the Collection#find method,
|
145
|
+
# and only the last skip applied has an effect.
|
146
|
+
#
|
147
|
+
# @return [Integer]
|
148
|
+
#
|
149
|
+
# @raise [InvalidOperation] if this cursor has already been used.
|
150
|
+
def skip(number_to_skip=nil)
|
151
|
+
return @skip unless number_to_skip
|
152
|
+
check_modifiable
|
153
|
+
raise ArgumentError, "skip requires an integer" unless number_to_skip.is_a? Integer
|
154
|
+
|
155
|
+
@skip = number_to_skip
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
159
|
+
# Iterate over each document in this cursor, yielding it to the given
|
160
|
+
# block.
|
161
|
+
#
|
162
|
+
# Iterating over an entire cursor will close it.
|
163
|
+
#
|
164
|
+
# @yield passes each document to a block for processing.
|
165
|
+
#
|
166
|
+
# @example if 'comments' represents a collection of comments:
|
167
|
+
# comments.find.each do |doc|
|
168
|
+
# puts doc['user']
|
169
|
+
# end
|
170
|
+
def each
|
171
|
+
num_returned = 0
|
172
|
+
while more? && (@limit <= 0 || num_returned < @limit)
|
173
|
+
yield next_document
|
174
|
+
num_returned += 1
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Receive all the documents from this cursor as an array of hashes.
|
179
|
+
#
|
180
|
+
# Note: use of this method is discouraged - in most cases, it's much more
|
181
|
+
# efficient to retrieve documents as you need them by iterating over the cursor.
|
182
|
+
#
|
183
|
+
# @return [Array] an array of documents.
|
184
|
+
#
|
185
|
+
# @raise [InvalidOperation] if this cursor has already been used or if
|
186
|
+
# this method has already been called on the cursor.
|
187
|
+
def to_a
|
188
|
+
raise InvalidOperation, "can't call Cursor#to_a on a used cursor" if @query_run
|
189
|
+
rows = []
|
190
|
+
num_returned = 0
|
191
|
+
while more? && (@limit <= 0 || num_returned < @limit)
|
192
|
+
rows << next_document
|
193
|
+
num_returned += 1
|
194
|
+
end
|
195
|
+
rows
|
196
|
+
end
|
197
|
+
|
198
|
+
# Get the explain plan for this cursor.
|
199
|
+
#
|
200
|
+
# @return [Hash] a document containing the explain plan for this cursor.
|
201
|
+
#
|
202
|
+
# @core explain explain-instance_method
|
203
|
+
def explain
|
204
|
+
c = Cursor.new(@collection, query_options_hash.merge(:limit => -@limit.abs, :explain => true))
|
205
|
+
explanation = c.next_document
|
206
|
+
c.close
|
207
|
+
|
208
|
+
explanation
|
209
|
+
end
|
210
|
+
|
211
|
+
# Close the cursor.
|
212
|
+
#
|
213
|
+
# Note: if a cursor is read until exhausted (read until Mongo::Constants::OP_QUERY or
|
214
|
+
# Mongo::Constants::OP_GETMORE returns zero for the cursor id), there is no need to
|
215
|
+
# close it manually.
|
216
|
+
#
|
217
|
+
# Note also: Collection#find takes an optional block argument which can be used to
|
218
|
+
# ensure that your cursors get closed.
|
219
|
+
#
|
220
|
+
# @return [True]
|
221
|
+
def close
|
222
|
+
if @cursor_id
|
223
|
+
message = ByteBuffer.new([0, 0, 0, 0])
|
224
|
+
message.put_int(1)
|
225
|
+
message.put_long(@cursor_id)
|
226
|
+
@connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, "cursor.close()")
|
227
|
+
end
|
228
|
+
@cursor_id = 0
|
229
|
+
@closed = true
|
230
|
+
end
|
231
|
+
|
232
|
+
# Is this cursor closed?
|
233
|
+
#
|
234
|
+
# @return [Boolean]
|
235
|
+
def closed?; @closed; end
|
236
|
+
|
237
|
+
# Returns an integer indicating which query options have been selected.
|
238
|
+
#
|
239
|
+
# @return [Integer]
|
240
|
+
#
|
241
|
+
# @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
|
242
|
+
# The MongoDB wire protocol.
|
243
|
+
def query_opts
|
244
|
+
timeout = @timeout ? 0 : Mongo::Constants::OP_QUERY_NO_CURSOR_TIMEOUT
|
245
|
+
slave_ok = @connection.slave_ok? ? Mongo::Constants::OP_QUERY_SLAVE_OK : 0
|
246
|
+
slave_ok + timeout
|
247
|
+
end
|
248
|
+
|
249
|
+
# Get the query options for this Cursor.
|
250
|
+
#
|
251
|
+
# @return [Hash]
|
252
|
+
def query_options_hash
|
253
|
+
{ :selector => @selector,
|
254
|
+
:fields => @fields,
|
255
|
+
:admin => @admin,
|
256
|
+
:skip => @skip_num,
|
257
|
+
:limit => @limit_num,
|
258
|
+
:order => @order,
|
259
|
+
:hint => @hint,
|
260
|
+
:snapshot => @snapshot,
|
261
|
+
:timeout => @timeout }
|
262
|
+
end
|
263
|
+
|
264
|
+
private
|
265
|
+
|
266
|
+
# Convert the +:fields+ parameter from a single field name or an array
|
267
|
+
# of fields names to a hash, with the field names for keys and '1' for each
|
268
|
+
# value.
|
269
|
+
def convert_fields_for_query(fields)
|
270
|
+
case fields
|
271
|
+
when String, Symbol
|
272
|
+
{fields => 1}
|
273
|
+
when Array
|
274
|
+
return nil if fields.length.zero?
|
275
|
+
returning({}) do |hash|
|
276
|
+
fields.each { |field| hash[field] = 1 }
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Set the query selector hash. If the selector is a Code or String object,
|
282
|
+
# the selector will be used in a $where clause.
|
283
|
+
# See http://www.mongodb.org/display/DOCS/Server-side+Code+Execution
|
284
|
+
def convert_selector_for_query(selector)
|
285
|
+
case selector
|
286
|
+
when Hash
|
287
|
+
selector
|
288
|
+
when nil
|
289
|
+
{}
|
290
|
+
when String
|
291
|
+
{"$where" => Code.new(selector)}
|
292
|
+
when Code
|
293
|
+
{"$where" => selector}
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Returns true if the query contains order, explain, hint, or snapshot.
|
298
|
+
def query_contains_special_fields?
|
299
|
+
@order || @explain || @hint || @snapshot
|
300
|
+
end
|
301
|
+
|
302
|
+
# Return a number of documents remaining for this cursor.
|
303
|
+
def num_remaining
|
304
|
+
refill_via_get_more if @cache.length == 0
|
305
|
+
@cache.length
|
306
|
+
end
|
307
|
+
|
308
|
+
# Internal method, not for general use. Return +true+ if there are
|
309
|
+
# more records to retrieve. This method does not check @limit;
|
310
|
+
# Cursor#each is responsible for doing that.
|
311
|
+
def more?
|
312
|
+
num_remaining > 0
|
313
|
+
end
|
314
|
+
|
315
|
+
def refill_via_get_more
|
316
|
+
return if send_initial_query || @cursor_id.zero?
|
317
|
+
message = ByteBuffer.new([0, 0, 0, 0])
|
318
|
+
|
319
|
+
# DB name.
|
320
|
+
db_name = @admin ? 'admin' : @db.name
|
321
|
+
BSON_RUBY.serialize_cstr(message, "#{db_name}.#{@collection.name}")
|
322
|
+
|
323
|
+
# Number of results to return; db decides for now.
|
324
|
+
message.put_int(0)
|
325
|
+
|
326
|
+
# Cursor id.
|
327
|
+
message.put_long(@cursor_id)
|
328
|
+
results, @n_received, @cursor_id = @connection.receive_message(Mongo::Constants::OP_GET_MORE, message, "cursor.get_more()", @socket)
|
329
|
+
@cache += results
|
330
|
+
close_cursor_if_query_complete
|
331
|
+
end
|
332
|
+
|
333
|
+
# Run query the first time we request an object from the wire
|
334
|
+
def send_initial_query
|
335
|
+
if @query_run
|
336
|
+
false
|
337
|
+
else
|
338
|
+
message = construct_query_message
|
339
|
+
results, @n_received, @cursor_id = @connection.receive_message(Mongo::Constants::OP_QUERY, message,
|
340
|
+
(query_log_message if @connection.logger), @socket)
|
341
|
+
@cache += results
|
342
|
+
@query_run = true
|
343
|
+
close_cursor_if_query_complete
|
344
|
+
true
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def construct_query_message
|
349
|
+
message = ByteBuffer.new
|
350
|
+
message.put_int(query_opts)
|
351
|
+
db_name = @admin ? 'admin' : @db.name
|
352
|
+
BSON_RUBY.serialize_cstr(message, "#{db_name}.#{@collection.name}")
|
353
|
+
message.put_int(@skip)
|
354
|
+
message.put_int(@limit)
|
355
|
+
selector = @selector
|
356
|
+
if query_contains_special_fields?
|
357
|
+
selector = selector_with_special_query_fields
|
358
|
+
end
|
359
|
+
message.put_array(BSON.serialize(selector, false).to_a)
|
360
|
+
message.put_array(BSON.serialize(@fields, false).to_a) if @fields
|
361
|
+
message
|
362
|
+
end
|
363
|
+
|
364
|
+
def query_log_message
|
365
|
+
"#{@admin ? 'admin' : @db.name}.#{@collection.name}.find(#{@selector.inspect}, #{@fields ? @fields.inspect : '{}'})" +
|
366
|
+
"#{@skip != 0 ? ('.skip(' + @skip.to_s + ')') : ''}#{@limit != 0 ? ('.limit(' + @limit.to_s + ')') : ''}"
|
367
|
+
end
|
368
|
+
|
369
|
+
def selector_with_special_query_fields
|
370
|
+
sel = OrderedHash.new
|
371
|
+
sel['query'] = @selector
|
372
|
+
sel['orderby'] = formatted_order_clause if @order
|
373
|
+
sel['$hint'] = @hint if @hint && @hint.length > 0
|
374
|
+
sel['$explain'] = true if @explain
|
375
|
+
sel['$snapshot'] = true if @snapshot
|
376
|
+
sel
|
377
|
+
end
|
378
|
+
|
379
|
+
def formatted_order_clause
|
380
|
+
case @order
|
381
|
+
when String, Symbol then string_as_sort_parameters(@order)
|
382
|
+
when Array then array_as_sort_parameters(@order)
|
383
|
+
else
|
384
|
+
raise InvalidSortValueError, "Illegal sort clause, '#{@order.class.name}'; must be of the form " +
|
385
|
+
"[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
def to_s
|
390
|
+
"DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)"
|
391
|
+
end
|
392
|
+
|
393
|
+
def close_cursor_if_query_complete
|
394
|
+
close if @limit > 0 && @n_received >= @limit
|
395
|
+
end
|
396
|
+
|
397
|
+
def check_modifiable
|
398
|
+
if @query_run || @closed
|
399
|
+
raise InvalidOperation, "Cannot modify the query once it has been run or closed."
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|