mongo 1.1.5 → 1.3.0

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 (76) hide show
  1. data/README.md +15 -15
  2. data/Rakefile +38 -17
  3. data/docs/FAQ.md +4 -0
  4. data/docs/HISTORY.md +59 -0
  5. data/docs/RELEASES.md +33 -0
  6. data/docs/REPLICA_SETS.md +13 -16
  7. data/lib/mongo/collection.rb +157 -69
  8. data/lib/mongo/connection.rb +189 -65
  9. data/lib/mongo/cursor.rb +43 -29
  10. data/lib/mongo/db.rb +63 -43
  11. data/lib/mongo/exceptions.rb +4 -1
  12. data/lib/mongo/gridfs/grid.rb +1 -1
  13. data/lib/mongo/gridfs/grid_ext.rb +1 -1
  14. data/lib/mongo/gridfs/grid_file_system.rb +1 -1
  15. data/lib/mongo/gridfs/grid_io.rb +89 -8
  16. data/lib/mongo/gridfs/grid_io_fix.rb +1 -1
  17. data/lib/mongo/repl_set_connection.rb +72 -20
  18. data/lib/mongo/test.rb +20 -0
  19. data/lib/mongo/util/conversions.rb +1 -1
  20. data/lib/mongo/util/core_ext.rb +11 -1
  21. data/lib/mongo/util/pool.rb +67 -15
  22. data/lib/mongo/util/server_version.rb +1 -1
  23. data/lib/mongo/util/support.rb +1 -1
  24. data/lib/mongo/util/uri_parser.rb +127 -13
  25. data/lib/mongo.rb +38 -2
  26. data/test/async/collection_test.rb +224 -0
  27. data/test/async/connection_test.rb +24 -0
  28. data/test/async/cursor_test.rb +162 -0
  29. data/test/async/worker_pool_test.rb +99 -0
  30. data/test/auxillary/fork_test.rb +30 -0
  31. data/test/auxillary/repl_set_auth_test.rb +58 -0
  32. data/test/auxillary/threaded_authentication_test.rb +101 -0
  33. data/test/bson/bson_test.rb +140 -28
  34. data/test/bson/byte_buffer_test.rb +18 -0
  35. data/test/bson/object_id_test.rb +14 -1
  36. data/test/bson/ordered_hash_test.rb +7 -0
  37. data/test/bson/timestamp_test.rb +24 -0
  38. data/test/collection_test.rb +104 -15
  39. data/test/connection_test.rb +78 -2
  40. data/test/conversions_test.rb +10 -11
  41. data/test/cursor_fail_test.rb +1 -1
  42. data/test/cursor_message_test.rb +1 -1
  43. data/test/cursor_test.rb +33 -4
  44. data/test/db_api_test.rb +30 -52
  45. data/test/db_test.rb +3 -3
  46. data/test/grid_file_system_test.rb +0 -1
  47. data/test/grid_io_test.rb +72 -1
  48. data/test/grid_test.rb +16 -16
  49. data/test/load/resque/load.rb +21 -0
  50. data/test/load/resque/processor.rb +26 -0
  51. data/test/load/thin/load.rb +24 -0
  52. data/test/load/unicorn/load.rb +23 -0
  53. data/test/load/unicorn/unicorn.rb +29 -0
  54. data/test/replica_sets/connect_test.rb +11 -1
  55. data/test/replica_sets/connection_string_test.rb +32 -0
  56. data/test/replica_sets/query_secondaries.rb +16 -0
  57. data/test/replica_sets/query_test.rb +10 -0
  58. data/test/replica_sets/replication_ack_test.rb +2 -0
  59. data/test/replica_sets/rs_test_helper.rb +9 -11
  60. data/test/support/hash_with_indifferent_access.rb +0 -13
  61. data/test/support_test.rb +0 -1
  62. data/test/test_helper.rb +27 -8
  63. data/test/tools/auth_repl_set_manager.rb +14 -0
  64. data/test/tools/load.rb +58 -0
  65. data/test/tools/repl_set_manager.rb +34 -9
  66. data/test/tools/sharding_manager.rb +202 -0
  67. data/test/tools/test.rb +3 -12
  68. data/test/unit/collection_test.rb +20 -24
  69. data/test/unit/connection_test.rb +4 -18
  70. data/test/unit/cursor_test.rb +16 -6
  71. data/test/unit/db_test.rb +10 -11
  72. data/test/unit/repl_set_connection_test.rb +0 -23
  73. data/test/unit/safe_test.rb +3 -3
  74. data/test/uri_test.rb +91 -0
  75. metadata +49 -12
  76. data/docs/1.0_UPGRADE.md +0 -21
@@ -1,7 +1,7 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  # --
4
- # Copyright (C) 2008-2010 10gen Inc.
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ module Mongo
35
35
  STANDARD_HEADER_SIZE = 16
36
36
  RESPONSE_HEADER_SIZE = 20
37
37
 
38
- attr_reader :logger, :size, :auths, :primary, :safe, :primary_pool, :host_to_try
38
+ attr_reader :logger, :size, :auths, :primary, :safe, :primary_pool, :host_to_try, :pool_size
39
39
 
40
40
  # Counter for generating unique request ids.
41
41
  @@current_request_id = 0
@@ -55,18 +55,21 @@ module Mongo
55
55
  # @param [String, Hash] host.
56
56
  # @param [Integer] port specify a port number here if only one host is being specified.
57
57
  #
58
- # @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
58
+ # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
59
59
  # propogated to DB objects instantiated off of this Connection. This
60
60
  # default can be overridden upon instantiation of any DB by explicity setting a :safe value
61
61
  # on initialization.
62
- # @option options [Boolean] :slave_ok (false) Must be set to +true+ when connecting
62
+ # @option opts [Boolean] :slave_ok (false) Must be set to +true+ when connecting
63
63
  # to a single, slave node.
64
- # @option options [Logger, #debug] :logger (nil) Logger instance to receive driver operation log.
65
- # @option options [Integer] :pool_size (1) The maximum number of socket connections allowed per
64
+ # @option opts [Logger, #debug] :logger (nil) A Logger instance for debugging driver ops. Note that
65
+ # logging negatively impacts performance; therefore, it should not be used for high-performance apps.
66
+ # @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
66
67
  # connection pool. Note: this setting is relevant only for multi-threaded applications.
67
- # @option options [Float] :timeout (5.0) When all of the connections a pool are checked out,
68
+ # @option opts [Float] :timeout (5.0) When all of the connections a pool are checked out,
68
69
  # this is the number of seconds to wait for a new connection to be released before throwing an exception.
69
70
  # Note: this setting is relevant only for multi-threaded applications (which in Ruby are rare).
71
+ # @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
72
+ # Disabled by default.
70
73
  #
71
74
  # @example localhost, 27017
72
75
  # Connection.new
@@ -86,16 +89,16 @@ module Mongo
86
89
  # driver fails to connect to a replica set with that name.
87
90
  #
88
91
  # @core connections
89
- def initialize(host=nil, port=nil, options={})
92
+ def initialize(host=nil, port=nil, opts={})
90
93
  @host_to_try = format_pair(host, port)
91
94
 
92
95
  # Host and port of current master.
93
96
  @host = @port = nil
94
97
 
95
98
  # slave_ok can be true only if one node is specified
96
- @slave_ok = options[:slave_ok]
99
+ @slave_ok = opts[:slave_ok]
97
100
 
98
- setup(options)
101
+ setup(opts)
99
102
  end
100
103
 
101
104
  # DEPRECATED
@@ -109,9 +112,9 @@ module Mongo
109
112
  # @param nodes [Array] An array of arrays, each of which specifies a host and port.
110
113
  # @param opts [Hash] Any of the available options that can be passed to Connection.new.
111
114
  #
112
- # @option options [String] :rs_name (nil) The name of the replica set to connect to. An exception will be
115
+ # @option opts [String] :rs_name (nil) The name of the replica set to connect to. An exception will be
113
116
  # raised if unable to connect to a replica set with this name.
114
- # @option options [Boolean] :read_secondary (false) When true, this connection object will pick a random slave
117
+ # @option opts [Boolean] :read_secondary (false) When true, this connection object will pick a random slave
115
118
  # to send reads to.
116
119
  #
117
120
  # @example
@@ -138,20 +141,38 @@ module Mongo
138
141
  #
139
142
  # @param opts Any of the options available for Connection.new
140
143
  #
141
- # @return [Mongo::Connection]
142
- def self.from_uri(uri, opts={})
143
- nodes, auths = Mongo::URIParser.parse(uri)
144
- opts.merge!({:auths => auths})
145
- if nodes.length == 1
146
- Connection.new(nodes[0][0], nodes[0][1], opts)
147
- elsif nodes.length > 1
148
- nodes << opts
149
- ReplSetConnection.new(*nodes)
144
+ # @return [Mongo::Connection, Mongo::ReplSetConnection]
145
+ def self.from_uri(string, extra_opts={})
146
+ uri = URIParser.new(string)
147
+ opts = uri.connection_options
148
+ opts.merge!(extra_opts)
149
+
150
+ if uri.nodes.length == 1
151
+ opts.merge!({:auths => uri.auths})
152
+ Connection.new(uri.nodes[0][0], uri.nodes[0][1], opts)
153
+ elsif uri.nodes.length > 1
154
+ nodes = uri.nodes.clone
155
+ nodes_with_opts = nodes << opts
156
+ ReplSetConnection.new(*nodes_with_opts)
150
157
  else
151
158
  raise MongoArgumentError, "No nodes specified. Please ensure that you've provided at least one node."
152
159
  end
153
160
  end
154
161
 
162
+ # The host name used for this connection.
163
+ #
164
+ # @return [String]
165
+ def host
166
+ @primary_pool.host
167
+ end
168
+
169
+ # The port used for this connection.
170
+ #
171
+ # @return [Integer]
172
+ def port
173
+ @primary_pool.port
174
+ end
175
+
155
176
  # Fsync, then lock the mongod process against writes. Use this to get
156
177
  # the datafiles in a state safe for snapshotting, backing up, etc.
157
178
  #
@@ -184,10 +205,11 @@ module Mongo
184
205
  #
185
206
  # @raise [AuthenticationError] raises an exception if any one
186
207
  # authentication fails.
187
- def apply_saved_authentication
208
+ def apply_saved_authentication(opts={})
188
209
  return false if @auths.empty?
189
210
  @auths.each do |auth|
190
- self[auth['db_name']].authenticate(auth['username'], auth['password'], false)
211
+ self[auth['db_name']].issue_authentication(auth['username'], auth['password'], false,
212
+ :socket => opts[:socket])
191
213
  end
192
214
  true
193
215
  end
@@ -237,6 +259,14 @@ module Mongo
237
259
  true
238
260
  end
239
261
 
262
+ def authenticate_pools
263
+ @primary_pool.authenticate_existing
264
+ end
265
+
266
+ def logout_pools(db)
267
+ @primary_pool.logout_existing(db)
268
+ end
269
+
240
270
  # Return a hash with all database names
241
271
  # and their respective sizes on disk.
242
272
  #
@@ -259,12 +289,13 @@ module Mongo
259
289
  # See DB#new for valid options hash parameters.
260
290
  #
261
291
  # @param [String] db_name a valid database name.
292
+ # @param [Hash] opts options to be passed to the DB constructor.
262
293
  #
263
294
  # @return [Mongo::DB]
264
295
  #
265
296
  # @core databases db-instance_method
266
- def db(db_name, options={})
267
- DB.new(db_name, self, options)
297
+ def db(db_name, opts={})
298
+ DB.new(db_name, self, opts)
268
299
  end
269
300
 
270
301
  # Shortcut for returning a database. Use DB#db to accept options.
@@ -314,13 +345,22 @@ module Mongo
314
345
  self["admin"].command(oh)
315
346
  end
316
347
 
317
- # Get the build information for the current connection.
348
+ # Checks if a server is alive. This command will return immediately
349
+ # even if the server is in a lock.
350
+ #
351
+ # @return [Hash]
352
+ def ping
353
+ self["admin"].command({:ping => 1})
354
+ end
355
+
356
+ # Get the build information for the current connection.
318
357
  #
319
358
  # @return [Hash]
320
359
  def server_info
321
360
  self["admin"].command({:buildinfo => 1})
322
361
  end
323
362
 
363
+
324
364
  # Get the build version of the current server.
325
365
  #
326
366
  # @return [Mongo::ServerVersion]
@@ -406,7 +446,13 @@ module Mongo
406
446
  request_id = add_message_headers(message, operation)
407
447
  packed_message = message.to_s
408
448
  begin
409
- sock = socket || (command ? checkout_writer : checkout_reader)
449
+ if socket
450
+ sock = socket
451
+ checkin = false
452
+ else
453
+ sock = (command ? checkout_writer : checkout_reader)
454
+ checkin = true
455
+ end
410
456
 
411
457
  result = ''
412
458
  @safe_mutexes[sock].synchronize do
@@ -414,7 +460,9 @@ module Mongo
414
460
  result = receive(sock, request_id)
415
461
  end
416
462
  ensure
417
- command ? checkin_writer(sock) : checkin_reader(sock)
463
+ if checkin
464
+ command ? checkin_writer(sock) : checkin_reader(sock)
465
+ end
418
466
  end
419
467
  result
420
468
  end
@@ -427,17 +475,26 @@ module Mongo
427
475
  #
428
476
  # @raise [ConnectionFailure] if unable to connect to any host or port.
429
477
  def connect
430
- reset_connection
478
+ close
431
479
 
432
480
  config = check_is_master(@host_to_try)
433
- if is_primary?(config)
481
+ if config
482
+ if config['ismaster'] == 1 || config['ismaster'] == true
483
+ @read_primary = true
484
+ elsif @slave_ok
485
+ @read_primary = false
486
+ end
487
+
434
488
  set_primary(@host_to_try)
435
489
  end
436
490
 
437
- if !connected?
491
+ if connected?
492
+ BSON::BSON_CODER.update_max_bson_size(self)
493
+ else
438
494
  raise ConnectionFailure, "Failed to connect to a master node at #{@host_to_try[0]}:#{@host_to_try[1]}"
439
495
  end
440
496
  end
497
+ alias :reconnect :connect
441
498
 
442
499
  def connecting?
443
500
  @nodes_to_try.length > 0
@@ -450,10 +507,45 @@ module Mongo
450
507
  @primary_pool && @primary_pool.host && @primary_pool.port
451
508
  end
452
509
 
510
+ # Determine if the connection is active. In a normal case the *server_info* operation
511
+ # will be performed without issues, but if the connection was dropped by the server or
512
+ # for some reason the sockets are unsynchronized, a ConnectionFailure will be raised and
513
+ # the return will be false.
514
+ #
515
+ # @return [Boolean]
516
+ def active?
517
+ return false unless connected?
518
+
519
+ ping
520
+ true
521
+
522
+ rescue ConnectionFailure
523
+ false
524
+ end
525
+
526
+ # Determine whether we're reading from a primary node. If false,
527
+ # this connection connects to a secondary node and @slave_ok is true.
528
+ #
529
+ # @return [Boolean]
530
+ def read_primary?
531
+ @read_primary
532
+ end
533
+ alias :primary? :read_primary?
534
+
453
535
  # Close the connection to the database.
454
536
  def close
455
537
  @primary_pool.close if @primary_pool
456
538
  @primary_pool = nil
539
+ @primary = nil
540
+ end
541
+
542
+ # Returns the maximum BSON object size as returned by the core server.
543
+ # Use the 4MB default when the server doesn't report this.
544
+ #
545
+ # @return [Integer]
546
+ def max_bson_size
547
+ config = self['admin'].command({:ismaster => 1})
548
+ config['maxBsonObjectSize'] || Mongo::DEFAULT_MAX_BSON_SIZE
457
549
  end
458
550
 
459
551
  # Checkout a socket for reading (i.e., a secondary node).
@@ -486,26 +578,37 @@ module Mongo
486
578
  end
487
579
  end
488
580
 
581
+ # Execute the block and log the operation described by name
582
+ # and payload.
583
+ # TODO: Not sure if this should take a block.
584
+ def instrument(name, payload = {}, &blk)
585
+ res = yield
586
+ log_operation(name, payload)
587
+ res
588
+ end
589
+
489
590
  protected
490
591
 
491
592
  # Generic initialization code.
492
- # @protected
493
- def setup(options)
593
+ def setup(opts)
494
594
  # Authentication objects
495
- @auths = options.fetch(:auths, [])
595
+ @auths = opts.fetch(:auths, [])
496
596
 
497
597
  # Lock for request ids.
498
598
  @id_lock = Mutex.new
499
599
 
500
600
  # Pool size and timeout.
501
- @pool_size = options[:pool_size] || 1
502
- @timeout = options[:timeout] || 5.0
601
+ @pool_size = opts[:pool_size] || 1
602
+ @timeout = opts[:timeout] || 5.0
603
+
604
+ # Timeout on socket read operation.
605
+ @op_timeout = opts[:op_timeout] || nil
503
606
 
504
607
  # Mutex for synchronizing pool access
505
608
  @connection_mutex = Mutex.new
506
609
 
507
610
  # Global safe option. This is false by default.
508
- @safe = options[:safe] || false
611
+ @safe = opts[:safe] || false
509
612
 
510
613
  # Create a mutex when a new key, in this case a socket,
511
614
  # is added to the hash.
@@ -518,9 +621,14 @@ module Mongo
518
621
  @primary = nil
519
622
  @primary_pool = nil
520
623
 
521
- @logger = options[:logger] || nil
624
+ @logger = opts[:logger] || nil
522
625
 
523
- should_connect = options.fetch(:connect, true)
626
+ if @logger
627
+ @logger.debug("MongoDB logging. Please note that logging negatively impacts performance " +
628
+ "and should be disabled for high-performance production apps.")
629
+ end
630
+
631
+ should_connect = opts.fetch(:connect, true)
524
632
  connect if should_connect
525
633
  end
526
634
 
@@ -540,6 +648,18 @@ module Mongo
540
648
  end
541
649
  end
542
650
 
651
+ ## Logging methods
652
+
653
+ def log_operation(name, payload)
654
+ return unless @logger
655
+ msg = "#{payload[:database]}['#{payload[:collection]}'].#{name}("
656
+ msg += payload.values_at(:selector, :document, :documents, :fields ).compact.map(&:inspect).join(', ') + ")"
657
+ msg += ".skip(#{payload[:skip]})" if payload[:skip]
658
+ msg += ".limit(#{payload[:limit]})" if payload[:limit]
659
+ msg += ".sort(#{payload[:order]})" if payload[:order]
660
+ @logger.debug "MONGODB #{msg}"
661
+ end
662
+
543
663
  private
544
664
 
545
665
  ## Methods for establishing a connection:
@@ -549,17 +669,6 @@ module Mongo
549
669
  # TODO: evaluate whether this method is actually necessary
550
670
  def reset_connection
551
671
  close
552
- @primary = nil
553
- end
554
-
555
- # Primary is defined as either a master node or a slave if
556
- # :slave_ok has been set to +true+.
557
- #
558
- # If a primary node is discovered, we set the the @host and @port and
559
- # apply any saved authentication.
560
- # TODO: simplify
561
- def is_primary?(config)
562
- config && (config['ismaster'] == 1 || config['ismaster'] == true) || @slave_ok
563
672
  end
564
673
 
565
674
  def check_is_master(node)
@@ -568,7 +677,7 @@ module Mongo
568
677
  socket = TCPSocket.new(host, port)
569
678
  socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
570
679
 
571
- config = self['admin'].command({:ismaster => 1}, :sock => socket)
680
+ config = self['admin'].command({:ismaster => 1}, :socket => socket)
572
681
  rescue OperationFailure, SocketError, SystemCallError, IOError => ex
573
682
  close
574
683
  ensure
@@ -578,16 +687,13 @@ module Mongo
578
687
  config
579
688
  end
580
689
 
581
- # Set the specified node as primary, and
582
- # apply any saved authentication credentials.
690
+ # Set the specified node as primary.
583
691
  def set_primary(node)
584
692
  host, port = *node
585
693
  @primary = [host, port]
586
694
  @primary_pool = Pool.new(self, host, port, :size => @pool_size, :timeout => @timeout)
587
- apply_saved_authentication
588
695
  end
589
696
 
590
-
591
697
  ## Low-level connection methods.
592
698
 
593
699
  def receive(sock, expected_response)
@@ -673,7 +779,7 @@ module Mongo
673
779
  #
674
780
  # Note: this method modifies message by reference.
675
781
  #
676
- # @returns [Integer] the request id used in the header
782
+ # @return [Integer] the request id used in the header
677
783
  def add_message_headers(message, operation)
678
784
  headers = [
679
785
  # Message size.
@@ -731,19 +837,37 @@ module Mongo
731
837
  # Requires length and an available socket.
732
838
  def receive_message_on_socket(length, socket)
733
839
  begin
734
- message = socket.read(length)
735
- raise ConnectionFailure, "connection closed" unless message.length > 0
736
- if message.length < length
737
- chunk = new_binary_string
738
- while message.length < length
739
- socket.read(length - message.length, chunk)
740
- raise ConnectionFailure, "connection closed" unless chunk.length > 0
741
- message << chunk
840
+ if @op_timeout
841
+ message = nil
842
+ Mongo::TimeoutHandler.timeout(@op_timeout, OperationTimeout) do
843
+ message = receive_data(length, socket)
742
844
  end
845
+ else
846
+ message = receive_data(length, socket)
743
847
  end
744
848
  rescue => ex
745
849
  close
746
- raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
850
+
851
+ if ex.class == OperationTimeout
852
+ raise OperationTimeout, "Timed out waiting on socket read."
853
+ else
854
+ raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
855
+ end
856
+ end
857
+ message
858
+ end
859
+
860
+ def receive_data(length, socket)
861
+ message = new_binary_string
862
+ socket.read(length, message)
863
+ raise ConnectionFailure, "connection closed" unless message && message.length > 0
864
+ if message.length < length
865
+ chunk = new_binary_string
866
+ while message.length < length
867
+ socket.read(length - message.length, chunk)
868
+ raise ConnectionFailure, "connection closed" unless chunk.length > 0
869
+ message << chunk
870
+ end
747
871
  end
748
872
  message
749
873
  end
data/lib/mongo/cursor.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
 
3
- # Copyright (C) 2008-2010 10gen Inc.
3
+ # Copyright (C) 2008-2011 10gen Inc.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ module Mongo
23
23
 
24
24
  attr_reader :collection, :selector, :fields,
25
25
  :order, :hint, :snapshot, :timeout,
26
- :full_collection_name
26
+ :full_collection_name, :transformer
27
27
 
28
28
  # Create a new cursor.
29
29
  #
@@ -33,26 +33,29 @@ module Mongo
33
33
  # @return [Cursor]
34
34
  #
35
35
  # @core cursors constructor_details
36
- def initialize(collection, options={})
36
+ def initialize(collection, opts={})
37
+ @cursor_id = nil
38
+
37
39
  @db = collection.db
38
40
  @collection = collection
39
41
  @connection = @db.connection
40
42
  @logger = @connection.logger
41
43
 
42
- @selector = options[:selector] || {}
43
- @fields = convert_fields_for_query(options[:fields])
44
- @skip = options[:skip] || 0
45
- @limit = options[:limit] || 0
46
- @order = options[:order]
47
- @hint = options[:hint]
48
- @snapshot = options[:snapshot]
49
- @timeout = options.has_key?(:timeout) ? options[:timeout] : true
50
- @explain = options[:explain]
51
- @socket = options[:socket]
52
- @tailable = options[:tailable] || false
44
+ @selector = opts[:selector] || {}
45
+ @fields = convert_fields_for_query(opts[:fields])
46
+ @skip = opts[:skip] || 0
47
+ @limit = opts[:limit] || 0
48
+ @order = opts[:order]
49
+ @hint = opts[:hint]
50
+ @snapshot = opts[:snapshot]
51
+ @timeout = opts.fetch(:timeout, true)
52
+ @explain = opts[:explain]
53
+ @socket = opts[:socket]
54
+ @tailable = opts[:tailable] || false
53
55
  @closed = false
54
56
  @query_run = false
55
- batch_size(options[:batch_size] || 0)
57
+ @transformer = opts[:transformer]
58
+ batch_size(opts[:batch_size] || 0)
56
59
 
57
60
  @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
58
61
  @cache = []
@@ -86,8 +89,13 @@ module Mongo
86
89
  raise OperationFailure, err
87
90
  end
88
91
 
89
- doc
92
+ if @transformer.nil?
93
+ doc
94
+ else
95
+ @transformer.call(doc) if doc
96
+ end
90
97
  end
98
+ alias :next :next_document
91
99
 
92
100
  # Reset this cursor on the server. Cursor options, such as the
93
101
  # query string and the values for skip and limit, are preserved.
@@ -307,8 +315,8 @@ module Mongo
307
315
  def query_options_hash
308
316
  { :selector => @selector,
309
317
  :fields => @fields,
310
- :skip => @skip_num,
311
- :limit => @limit_num,
318
+ :skip => @skip,
319
+ :limit => @limit,
312
320
  :order => @order,
313
321
  :hint => @hint,
314
322
  :snapshot => @snapshot,
@@ -373,18 +381,21 @@ module Mongo
373
381
  end
374
382
 
375
383
  # Run query the first time we request an object from the wire
384
+ # TODO: should we be calling instrument_payload even if logging
385
+ # is disabled?
376
386
  def send_initial_query
377
387
  if @query_run
378
388
  false
379
389
  else
380
390
  message = construct_query_message
381
- @logger.debug query_log_message if @logger
382
- results, @n_received, @cursor_id = @connection.receive_message(
383
- Mongo::Constants::OP_QUERY, message, nil, @socket, @command)
384
- @returned += @n_received
385
- @cache += results
386
- @query_run = true
387
- close_cursor_if_query_complete
391
+ @connection.instrument(:find, instrument_payload) do
392
+ results, @n_received, @cursor_id = @connection.receive_message(
393
+ Mongo::Constants::OP_QUERY, message, nil, @socket, @command)
394
+ @returned += @n_received
395
+ @cache += results
396
+ @query_run = true
397
+ close_cursor_if_query_complete
398
+ end
388
399
  true
389
400
  end
390
401
  end
@@ -401,10 +412,13 @@ module Mongo
401
412
  message
402
413
  end
403
414
 
404
- def query_log_message
405
- "#{@db.name}['#{@collection.name}'].find(#{@selector.inspect}, #{@fields ? @fields.inspect : '{}'})" +
406
- "#{@skip != 0 ? ('.skip(' + @skip.to_s + ')') : ''}#{@limit != 0 ? ('.limit(' + @limit.to_s + ')') : ''}" +
407
- "#{@order ? ('.sort(' + @order.inspect + ')') : ''}"
415
+ def instrument_payload
416
+ log = { :database => @db.name, :collection => @collection.name, :selector => selector }
417
+ log[:fields] = @fields if @fields
418
+ log[:skip] = @skip if @skip && (@skip > 0)
419
+ log[:limit] = @limit if @limit && (@limit > 0)
420
+ log[:order] = @order if @order
421
+ log
408
422
  end
409
423
 
410
424
  def construct_query_spec