kbaum-mongo 0.18.3p

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/LICENSE.txt +202 -0
  2. data/README.rdoc +339 -0
  3. data/Rakefile +138 -0
  4. data/bin/bson_benchmark.rb +59 -0
  5. data/bin/fail_if_no_c.rb +11 -0
  6. data/examples/admin.rb +42 -0
  7. data/examples/capped.rb +22 -0
  8. data/examples/cursor.rb +48 -0
  9. data/examples/gridfs.rb +88 -0
  10. data/examples/index_test.rb +126 -0
  11. data/examples/info.rb +31 -0
  12. data/examples/queries.rb +70 -0
  13. data/examples/simple.rb +24 -0
  14. data/examples/strict.rb +35 -0
  15. data/examples/types.rb +36 -0
  16. data/lib/mongo/collection.rb +609 -0
  17. data/lib/mongo/connection.rb +672 -0
  18. data/lib/mongo/cursor.rb +403 -0
  19. data/lib/mongo/db.rb +555 -0
  20. data/lib/mongo/exceptions.rb +66 -0
  21. data/lib/mongo/gridfs/chunk.rb +91 -0
  22. data/lib/mongo/gridfs/grid.rb +79 -0
  23. data/lib/mongo/gridfs/grid_file_system.rb +101 -0
  24. data/lib/mongo/gridfs/grid_io.rb +338 -0
  25. data/lib/mongo/gridfs/grid_store.rb +580 -0
  26. data/lib/mongo/gridfs.rb +25 -0
  27. data/lib/mongo/types/binary.rb +52 -0
  28. data/lib/mongo/types/code.rb +36 -0
  29. data/lib/mongo/types/dbref.rb +40 -0
  30. data/lib/mongo/types/min_max_keys.rb +58 -0
  31. data/lib/mongo/types/objectid.rb +180 -0
  32. data/lib/mongo/types/regexp_of_holding.rb +45 -0
  33. data/lib/mongo/util/bson_c.rb +18 -0
  34. data/lib/mongo/util/bson_ruby.rb +606 -0
  35. data/lib/mongo/util/byte_buffer.rb +222 -0
  36. data/lib/mongo/util/conversions.rb +87 -0
  37. data/lib/mongo/util/ordered_hash.rb +140 -0
  38. data/lib/mongo/util/server_version.rb +69 -0
  39. data/lib/mongo/util/support.rb +26 -0
  40. data/lib/mongo.rb +63 -0
  41. data/mongo-ruby-driver.gemspec +28 -0
  42. data/test/auxillary/autoreconnect_test.rb +42 -0
  43. data/test/binary_test.rb +15 -0
  44. data/test/bson_test.rb +427 -0
  45. data/test/byte_buffer_test.rb +81 -0
  46. data/test/chunk_test.rb +82 -0
  47. data/test/collection_test.rb +515 -0
  48. data/test/connection_test.rb +160 -0
  49. data/test/conversions_test.rb +120 -0
  50. data/test/cursor_test.rb +379 -0
  51. data/test/db_api_test.rb +780 -0
  52. data/test/db_connection_test.rb +16 -0
  53. data/test/db_test.rb +272 -0
  54. data/test/grid_file_system_test.rb +210 -0
  55. data/test/grid_io_test.rb +78 -0
  56. data/test/grid_store_test.rb +334 -0
  57. data/test/grid_test.rb +87 -0
  58. data/test/objectid_test.rb +125 -0
  59. data/test/ordered_hash_test.rb +172 -0
  60. data/test/replica/count_test.rb +34 -0
  61. data/test/replica/insert_test.rb +50 -0
  62. data/test/replica/pooled_insert_test.rb +54 -0
  63. data/test/replica/query_test.rb +39 -0
  64. data/test/slave_connection_test.rb +36 -0
  65. data/test/test_helper.rb +42 -0
  66. data/test/threading/test_threading_large_pool.rb +90 -0
  67. data/test/threading_test.rb +87 -0
  68. data/test/unit/collection_test.rb +61 -0
  69. data/test/unit/connection_test.rb +117 -0
  70. data/test/unit/cursor_test.rb +93 -0
  71. data/test/unit/db_test.rb +98 -0
  72. metadata +127 -0
@@ -0,0 +1,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