jonbell-mongo 1.3.1.2

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 (88) hide show
  1. data/LICENSE.txt +190 -0
  2. data/README.md +333 -0
  3. data/Rakefile +215 -0
  4. data/bin/mongo_console +21 -0
  5. data/docs/CREDITS.md +123 -0
  6. data/docs/FAQ.md +116 -0
  7. data/docs/GridFS.md +158 -0
  8. data/docs/HISTORY.md +263 -0
  9. data/docs/RELEASES.md +33 -0
  10. data/docs/REPLICA_SETS.md +72 -0
  11. data/docs/TUTORIAL.md +247 -0
  12. data/docs/WRITE_CONCERN.md +28 -0
  13. data/lib/mongo.rb +97 -0
  14. data/lib/mongo/collection.rb +895 -0
  15. data/lib/mongo/connection.rb +926 -0
  16. data/lib/mongo/cursor.rb +474 -0
  17. data/lib/mongo/db.rb +617 -0
  18. data/lib/mongo/exceptions.rb +71 -0
  19. data/lib/mongo/gridfs/grid.rb +107 -0
  20. data/lib/mongo/gridfs/grid_ext.rb +57 -0
  21. data/lib/mongo/gridfs/grid_file_system.rb +146 -0
  22. data/lib/mongo/gridfs/grid_io.rb +485 -0
  23. data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
  24. data/lib/mongo/repl_set_connection.rb +356 -0
  25. data/lib/mongo/util/conversions.rb +89 -0
  26. data/lib/mongo/util/core_ext.rb +60 -0
  27. data/lib/mongo/util/pool.rb +177 -0
  28. data/lib/mongo/util/server_version.rb +71 -0
  29. data/lib/mongo/util/support.rb +82 -0
  30. data/lib/mongo/util/uri_parser.rb +185 -0
  31. data/mongo.gemspec +34 -0
  32. data/test/auxillary/1.4_features.rb +166 -0
  33. data/test/auxillary/authentication_test.rb +68 -0
  34. data/test/auxillary/autoreconnect_test.rb +41 -0
  35. data/test/auxillary/fork_test.rb +30 -0
  36. data/test/auxillary/repl_set_auth_test.rb +58 -0
  37. data/test/auxillary/slave_connection_test.rb +36 -0
  38. data/test/auxillary/threaded_authentication_test.rb +101 -0
  39. data/test/bson/binary_test.rb +15 -0
  40. data/test/bson/bson_test.rb +654 -0
  41. data/test/bson/byte_buffer_test.rb +208 -0
  42. data/test/bson/hash_with_indifferent_access_test.rb +38 -0
  43. data/test/bson/json_test.rb +17 -0
  44. data/test/bson/object_id_test.rb +154 -0
  45. data/test/bson/ordered_hash_test.rb +210 -0
  46. data/test/bson/timestamp_test.rb +24 -0
  47. data/test/collection_test.rb +910 -0
  48. data/test/connection_test.rb +324 -0
  49. data/test/conversions_test.rb +119 -0
  50. data/test/cursor_fail_test.rb +75 -0
  51. data/test/cursor_message_test.rb +43 -0
  52. data/test/cursor_test.rb +483 -0
  53. data/test/db_api_test.rb +738 -0
  54. data/test/db_connection_test.rb +15 -0
  55. data/test/db_test.rb +315 -0
  56. data/test/grid_file_system_test.rb +259 -0
  57. data/test/grid_io_test.rb +209 -0
  58. data/test/grid_test.rb +258 -0
  59. data/test/load/thin/load.rb +24 -0
  60. data/test/load/unicorn/load.rb +23 -0
  61. data/test/replica_sets/connect_test.rb +112 -0
  62. data/test/replica_sets/connection_string_test.rb +32 -0
  63. data/test/replica_sets/count_test.rb +35 -0
  64. data/test/replica_sets/insert_test.rb +53 -0
  65. data/test/replica_sets/pooled_insert_test.rb +55 -0
  66. data/test/replica_sets/query_secondaries.rb +108 -0
  67. data/test/replica_sets/query_test.rb +51 -0
  68. data/test/replica_sets/replication_ack_test.rb +66 -0
  69. data/test/replica_sets/rs_test_helper.rb +27 -0
  70. data/test/safe_test.rb +68 -0
  71. data/test/support/hash_with_indifferent_access.rb +186 -0
  72. data/test/support/keys.rb +45 -0
  73. data/test/support_test.rb +18 -0
  74. data/test/test_helper.rb +102 -0
  75. data/test/threading/threading_with_large_pool_test.rb +90 -0
  76. data/test/threading_test.rb +87 -0
  77. data/test/tools/auth_repl_set_manager.rb +14 -0
  78. data/test/tools/repl_set_manager.rb +266 -0
  79. data/test/unit/collection_test.rb +130 -0
  80. data/test/unit/connection_test.rb +85 -0
  81. data/test/unit/cursor_test.rb +109 -0
  82. data/test/unit/db_test.rb +94 -0
  83. data/test/unit/grid_test.rb +49 -0
  84. data/test/unit/pool_test.rb +9 -0
  85. data/test/unit/repl_set_connection_test.rb +59 -0
  86. data/test/unit/safe_test.rb +125 -0
  87. data/test/uri_test.rb +91 -0
  88. metadata +224 -0
@@ -0,0 +1,474 @@
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, :transformer
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
+ @cursor_id = nil
38
+
39
+ @db = collection.db
40
+ @collection = collection
41
+ @connection = @db.connection
42
+ @logger = @connection.logger
43
+
44
+ # Query selector
45
+ @selector = opts[:selector] || {}
46
+
47
+ # Special operators that form part of $query
48
+ @order = opts[:order]
49
+ @explain = opts[:explain]
50
+ @hint = opts[:hint]
51
+ @snapshot = opts[:snapshot]
52
+ @max_scan = opts.fetch(:max_scan, nil)
53
+ @return_key = opts.fetch(:return_key, nil)
54
+ @show_disk_loc = opts.fetch(:show_disk_loc, nil)
55
+
56
+ # Wire-protocol settings
57
+ @fields = convert_fields_for_query(opts[:fields])
58
+ @skip = opts[:skip] || 0
59
+ @limit = opts[:limit] || 0
60
+ @tailable = opts[:tailable] || false
61
+ @timeout = opts.fetch(:timeout, true)
62
+
63
+ # Use this socket for the query
64
+ @socket = opts[:socket]
65
+
66
+ @closed = false
67
+ @query_run = false
68
+
69
+ @transformer = opts[:transformer]
70
+ batch_size(opts[:batch_size] || 0)
71
+
72
+ @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
73
+ @cache = []
74
+ @returned = 0
75
+
76
+ if @collection.name =~ /^\$cmd/ || @collection.name =~ /^system/
77
+ @command = true
78
+ else
79
+ @command = false
80
+ end
81
+ end
82
+
83
+ # Get the next document specified the cursor options.
84
+ #
85
+ # @return [Hash, Nil] the next document or Nil if no documents remain.
86
+ def next_document
87
+ refresh if @cache.length == 0
88
+ doc = @cache.shift
89
+
90
+ if doc && doc['$err']
91
+ err = doc['$err']
92
+
93
+ # If the server has stopped being the master (e.g., it's one of a
94
+ # pair but it has died or something like that) then we close that
95
+ # connection. The next request will re-open on master server.
96
+ if err == "not master"
97
+ @connection.close
98
+ raise ConnectionFailure, err
99
+ end
100
+
101
+ raise OperationFailure, err
102
+ end
103
+
104
+ if @transformer.nil?
105
+ doc
106
+ else
107
+ @transformer.call(doc) if doc
108
+ end
109
+ end
110
+ alias :next :next_document
111
+
112
+ # Reset this cursor on the server. Cursor options, such as the
113
+ # query string and the values for skip and limit, are preserved.
114
+ def rewind!
115
+ close
116
+ @cache.clear
117
+ @cursor_id = nil
118
+ @closed = false
119
+ @query_run = false
120
+ @n_received = nil
121
+ true
122
+ end
123
+
124
+ # Determine whether this cursor has any remaining results.
125
+ #
126
+ # @return [Boolean]
127
+ def has_next?
128
+ num_remaining > 0
129
+ end
130
+
131
+ # Get the size of the result set for this query.
132
+ #
133
+ # @param [Boolean] whether of not to take notice of skip and limit
134
+ #
135
+ # @return [Integer] the number of objects in the result set for this query.
136
+ #
137
+ # @raise [OperationFailure] on a database error.
138
+ def count(skip_and_limit = false)
139
+ command = BSON::OrderedHash["count", @collection.name, "query", @selector]
140
+
141
+ if skip_and_limit
142
+ command.merge!(BSON::OrderedHash["limit", @limit]) if @limit != 0
143
+ command.merge!(BSON::OrderedHash["skip", @skip]) if @skip != 0
144
+ end
145
+
146
+ command.merge!(BSON::OrderedHash["fields", @fields])
147
+
148
+ response = @db.command(command)
149
+ return response['n'].to_i if Mongo::Support.ok?(response)
150
+ return 0 if response['errmsg'] == "ns missing"
151
+ raise OperationFailure, "Count failed: #{response['errmsg']}"
152
+ end
153
+
154
+ # Sort this cursor's results.
155
+ #
156
+ # This method overrides any sort order specified in the Collection#find
157
+ # method, and only the last sort applied has an effect.
158
+ #
159
+ # @param [Symbol, Array] key_or_list either 1) a key to sort by or 2)
160
+ # an array of [key, direction] pairs to sort by. Direction should
161
+ # be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
162
+ #
163
+ # @raise [InvalidOperation] if this cursor has already been used.
164
+ #
165
+ # @raise [InvalidSortValueError] if the specified order is invalid.
166
+ def sort(key_or_list, direction=nil)
167
+ check_modifiable
168
+
169
+ if !direction.nil?
170
+ order = [[key_or_list, direction]]
171
+ else
172
+ order = key_or_list
173
+ end
174
+
175
+ @order = order
176
+ self
177
+ end
178
+
179
+ # Limit the number of results to be returned by this cursor.
180
+ #
181
+ # This method overrides any limit specified in the Collection#find method,
182
+ # and only the last limit applied has an effect.
183
+ #
184
+ # @return [Integer] the current number_to_return if no parameter is given.
185
+ #
186
+ # @raise [InvalidOperation] if this cursor has already been used.
187
+ #
188
+ # @core limit limit-instance_method
189
+ def limit(number_to_return=nil)
190
+ return @limit unless number_to_return
191
+ check_modifiable
192
+
193
+ @limit = number_to_return
194
+ self
195
+ end
196
+
197
+ # Skips the first +number_to_skip+ results of this cursor.
198
+ # Returns the current number_to_skip if no parameter is given.
199
+ #
200
+ # This method overrides any skip specified in the Collection#find method,
201
+ # and only the last skip applied has an effect.
202
+ #
203
+ # @return [Integer]
204
+ #
205
+ # @raise [InvalidOperation] if this cursor has already been used.
206
+ def skip(number_to_skip=nil)
207
+ return @skip unless number_to_skip
208
+ check_modifiable
209
+
210
+ @skip = number_to_skip
211
+ self
212
+ end
213
+
214
+ # Set the batch size for server responses.
215
+ #
216
+ # Note that the batch size will take effect only on queries
217
+ # where the number to be returned is greater than 100.
218
+ #
219
+ # @param [Integer] size either 0 or some integer greater than 1. If 0,
220
+ # the server will determine the batch size.
221
+ #
222
+ # @return [Cursor]
223
+ def batch_size(size=0)
224
+ check_modifiable
225
+ if size < 0 || size == 1
226
+ raise ArgumentError, "Invalid value for batch_size #{size}; must be 0 or > 1."
227
+ else
228
+ @batch_size = size > @limit ? @limit : size
229
+ end
230
+
231
+ self
232
+ end
233
+
234
+ # Iterate over each document in this cursor, yielding it to the given
235
+ # block.
236
+ #
237
+ # Iterating over an entire cursor will close it.
238
+ #
239
+ # @yield passes each document to a block for processing.
240
+ #
241
+ # @example if 'comments' represents a collection of comments:
242
+ # comments.find.each do |doc|
243
+ # puts doc['user']
244
+ # end
245
+ def each
246
+ #num_returned = 0
247
+ #while has_next? && (@limit <= 0 || num_returned < @limit)
248
+ while doc = next_document
249
+ yield doc #next_document
250
+ #num_returned += 1
251
+ end
252
+ end
253
+
254
+ # Receive all the documents from this cursor as an array of hashes.
255
+ #
256
+ # Notes:
257
+ #
258
+ # If you've already started iterating over the cursor, the array returned
259
+ # by this method contains only the remaining documents. See Cursor#rewind! if you
260
+ # need to reset the cursor.
261
+ #
262
+ # Use of this method is discouraged - in most cases, it's much more
263
+ # efficient to retrieve documents as you need them by iterating over the cursor.
264
+ #
265
+ # @return [Array] an array of documents.
266
+ def to_a
267
+ super
268
+ end
269
+
270
+ # Get the explain plan for this cursor.
271
+ #
272
+ # @return [Hash] a document containing the explain plan for this cursor.
273
+ #
274
+ # @core explain explain-instance_method
275
+ def explain
276
+ c = Cursor.new(@collection, query_options_hash.merge(:limit => -@limit.abs, :explain => true))
277
+ explanation = c.next_document
278
+ c.close
279
+
280
+ explanation
281
+ end
282
+
283
+ # Close the cursor.
284
+ #
285
+ # Note: if a cursor is read until exhausted (read until Mongo::Constants::OP_QUERY or
286
+ # Mongo::Constants::OP_GETMORE returns zero for the cursor id), there is no need to
287
+ # close it manually.
288
+ #
289
+ # Note also: Collection#find takes an optional block argument which can be used to
290
+ # ensure that your cursors get closed.
291
+ #
292
+ # @return [True]
293
+ def close
294
+ if @cursor_id && @cursor_id != 0
295
+ message = BSON::ByteBuffer.new([0, 0, 0, 0])
296
+ message.put_int(1)
297
+ message.put_long(@cursor_id)
298
+ @logger.debug("MONGODB cursor.close #{@cursor_id}") if @logger
299
+ @connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, :connection => :reader)
300
+ end
301
+ @cursor_id = 0
302
+ @closed = true
303
+ end
304
+
305
+ # Is this cursor closed?
306
+ #
307
+ # @return [Boolean]
308
+ def closed?; @closed; end
309
+
310
+ # Returns an integer indicating which query options have been selected.
311
+ #
312
+ # @return [Integer]
313
+ #
314
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
315
+ # The MongoDB wire protocol.
316
+ def query_opts
317
+ opts = 0
318
+ opts |= Mongo::Constants::OP_QUERY_NO_CURSOR_TIMEOUT unless @timeout
319
+ opts |= Mongo::Constants::OP_QUERY_SLAVE_OK if @connection.slave_ok?
320
+ opts |= Mongo::Constants::OP_QUERY_TAILABLE if @tailable
321
+ opts
322
+ end
323
+
324
+ # Get the query options for this Cursor.
325
+ #
326
+ # @return [Hash]
327
+ def query_options_hash
328
+ { :selector => @selector,
329
+ :fields => @fields,
330
+ :skip => @skip,
331
+ :limit => @limit,
332
+ :order => @order,
333
+ :hint => @hint,
334
+ :snapshot => @snapshot,
335
+ :timeout => @timeout,
336
+ :max_scan => @max_scan,
337
+ :return_key => @return_key,
338
+ :show_disk_loc => @show_disk_loc }
339
+ end
340
+
341
+ # Clean output for inspect.
342
+ def inspect
343
+ "<Mongo::Cursor:0x#{object_id.to_s(16)} namespace='#{@db.name}.#{@collection.name}' " +
344
+ "@selector=#{@selector.inspect}>"
345
+ end
346
+
347
+ private
348
+
349
+ # Convert the +:fields+ parameter from a single field name or an array
350
+ # of fields names to a hash, with the field names for keys and '1' for each
351
+ # value.
352
+ def convert_fields_for_query(fields)
353
+ case fields
354
+ when String, Symbol
355
+ {fields => 1}
356
+ when Array
357
+ return nil if fields.length.zero?
358
+ fields.each_with_object({}) { |field, hash| hash[field] = 1 }
359
+ when Hash
360
+ return fields
361
+ end
362
+ end
363
+
364
+ # Return the number of documents remaining for this cursor.
365
+ def num_remaining
366
+ refresh if @cache.length == 0
367
+ @cache.length
368
+ end
369
+
370
+ def refresh
371
+ return if send_initial_query || @cursor_id.zero?
372
+ message = BSON::ByteBuffer.new([0, 0, 0, 0])
373
+
374
+ # DB name.
375
+ BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
376
+
377
+ # Number of results to return.
378
+ if @limit > 0
379
+ limit = @limit - @returned
380
+ if @batch_size > 0
381
+ limit = limit < @batch_size ? limit : @batch_size
382
+ end
383
+ message.put_int(limit)
384
+ else
385
+ message.put_int(@batch_size)
386
+ end
387
+
388
+ # Cursor id.
389
+ message.put_long(@cursor_id)
390
+ @logger.debug("MONGODB cursor.refresh() for cursor #{@cursor_id}") if @logger
391
+ results, @n_received, @cursor_id = @connection.receive_message(
392
+ Mongo::Constants::OP_GET_MORE, message, nil, @socket, @command)
393
+ @returned += @n_received
394
+ @cache += results
395
+ close_cursor_if_query_complete
396
+ end
397
+
398
+ # Run query the first time we request an object from the wire
399
+ # TODO: should we be calling instrument_payload even if logging
400
+ # is disabled?
401
+ def send_initial_query
402
+ if @query_run
403
+ false
404
+ else
405
+ message = construct_query_message
406
+ @connection.instrument(:find, instrument_payload) do
407
+ results, @n_received, @cursor_id = @connection.receive_message(
408
+ Mongo::Constants::OP_QUERY, message, nil, @socket, @command)
409
+ @returned += @n_received
410
+ @cache += results
411
+ @query_run = true
412
+ close_cursor_if_query_complete
413
+ end
414
+ true
415
+ end
416
+ end
417
+
418
+ def construct_query_message
419
+ message = BSON::ByteBuffer.new
420
+ message.put_int(query_opts)
421
+ BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
422
+ message.put_int(@skip)
423
+ message.put_int(@limit)
424
+ spec = query_contains_special_fields? ? construct_query_spec : @selector
425
+ message.put_binary(BSON::BSON_CODER.serialize(spec, false).to_s)
426
+ message.put_binary(BSON::BSON_CODER.serialize(@fields, false).to_s) if @fields
427
+ message
428
+ end
429
+
430
+ def instrument_payload
431
+ log = { :database => @db.name, :collection => @collection.name, :selector => selector }
432
+ log[:fields] = @fields if @fields
433
+ log[:skip] = @skip if @skip && (@skip > 0)
434
+ log[:limit] = @limit if @limit && (@limit > 0)
435
+ log[:order] = @order if @order
436
+ log
437
+ end
438
+
439
+ def construct_query_spec
440
+ return @selector if @selector.has_key?('$query')
441
+ spec = BSON::OrderedHash.new
442
+ spec['$query'] = @selector
443
+ spec['$orderby'] = Mongo::Support.format_order_clause(@order) if @order
444
+ spec['$hint'] = @hint if @hint && @hint.length > 0
445
+ spec['$explain'] = true if @explain
446
+ spec['$snapshot'] = true if @snapshot
447
+ spec['$maxscan'] = @max_scan if @max_scan
448
+ spec['$returnKey'] = true if @return_key
449
+ spec['$showDiskLoc'] = true if @show_disk_loc
450
+ spec
451
+ end
452
+
453
+ # Returns true if the query contains order, explain, hint, or snapshot.
454
+ def query_contains_special_fields?
455
+ @order || @explain || @hint || @snapshot
456
+ end
457
+
458
+ def to_s
459
+ "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)"
460
+ end
461
+
462
+ def close_cursor_if_query_complete
463
+ if @limit > 0 && @returned >= @limit
464
+ close
465
+ end
466
+ end
467
+
468
+ def check_modifiable
469
+ if @query_run || @closed
470
+ raise InvalidOperation, "Cannot modify the query once it has been run or closed."
471
+ end
472
+ end
473
+ end
474
+ end