kbaum-mongo 0.18.3p

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