mongo 1.3.1 → 1.4.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 (75) hide show
  1. data/README.md +9 -6
  2. data/Rakefile +3 -4
  3. data/docs/HISTORY.md +20 -2
  4. data/docs/READ_PREFERENCE.md +39 -0
  5. data/docs/RELEASES.md +1 -1
  6. data/docs/REPLICA_SETS.md +23 -2
  7. data/docs/TAILABLE_CURSORS.md +51 -0
  8. data/docs/TUTORIAL.md +4 -4
  9. data/docs/WRITE_CONCERN.md +5 -2
  10. data/lib/mongo.rb +7 -22
  11. data/lib/mongo/collection.rb +96 -29
  12. data/lib/mongo/connection.rb +107 -62
  13. data/lib/mongo/cursor.rb +136 -57
  14. data/lib/mongo/db.rb +26 -5
  15. data/lib/mongo/exceptions.rb +17 -1
  16. data/lib/mongo/gridfs/grid.rb +1 -1
  17. data/lib/mongo/repl_set_connection.rb +273 -156
  18. data/lib/mongo/util/logging.rb +42 -0
  19. data/lib/mongo/util/node.rb +183 -0
  20. data/lib/mongo/util/pool.rb +76 -13
  21. data/lib/mongo/util/pool_manager.rb +208 -0
  22. data/lib/mongo/util/ssl_socket.rb +38 -0
  23. data/lib/mongo/util/support.rb +9 -1
  24. data/lib/mongo/util/timeout.rb +42 -0
  25. data/lib/mongo/version.rb +3 -0
  26. data/mongo.gemspec +2 -2
  27. data/test/bson/binary_test.rb +1 -1
  28. data/test/bson/bson_string_test.rb +30 -0
  29. data/test/bson/bson_test.rb +6 -3
  30. data/test/bson/byte_buffer_test.rb +1 -1
  31. data/test/bson/hash_with_indifferent_access_test.rb +1 -1
  32. data/test/bson/json_test.rb +1 -1
  33. data/test/bson/object_id_test.rb +2 -18
  34. data/test/bson/ordered_hash_test.rb +38 -3
  35. data/test/bson/test_helper.rb +46 -0
  36. data/test/bson/timestamp_test.rb +32 -10
  37. data/test/collection_test.rb +89 -3
  38. data/test/connection_test.rb +35 -20
  39. data/test/cursor_test.rb +63 -2
  40. data/test/db_test.rb +12 -2
  41. data/test/pool_test.rb +21 -0
  42. data/test/replica_sets/connect_test.rb +26 -13
  43. data/test/replica_sets/connection_string_test.rb +1 -4
  44. data/test/replica_sets/count_test.rb +1 -0
  45. data/test/replica_sets/insert_test.rb +1 -0
  46. data/test/replica_sets/pooled_insert_test.rb +4 -1
  47. data/test/replica_sets/query_secondaries.rb +2 -1
  48. data/test/replica_sets/query_test.rb +2 -1
  49. data/test/replica_sets/read_preference_test.rb +43 -0
  50. data/test/replica_sets/refresh_test.rb +123 -0
  51. data/test/replica_sets/replication_ack_test.rb +9 -4
  52. data/test/replica_sets/rs_test_helper.rb +2 -2
  53. data/test/timeout_test.rb +14 -0
  54. data/test/tools/repl_set_manager.rb +134 -23
  55. data/test/unit/collection_test.rb +6 -8
  56. data/test/unit/connection_test.rb +4 -4
  57. data/test/unit/cursor_test.rb +23 -5
  58. data/test/unit/db_test.rb +2 -0
  59. data/test/unit/grid_test.rb +2 -0
  60. data/test/unit/node_test.rb +73 -0
  61. data/test/unit/pool_manager_test.rb +47 -0
  62. data/test/unit/read_test.rb +101 -0
  63. metadata +214 -138
  64. data/lib/mongo/test.rb +0 -20
  65. data/test/async/collection_test.rb +0 -224
  66. data/test/async/connection_test.rb +0 -24
  67. data/test/async/cursor_test.rb +0 -162
  68. data/test/async/worker_pool_test.rb +0 -99
  69. data/test/load/resque/load.rb +0 -21
  70. data/test/load/resque/processor.rb +0 -26
  71. data/test/load/unicorn/unicorn.rb +0 -29
  72. data/test/tools/load.rb +0 -58
  73. data/test/tools/sharding_manager.rb +0 -202
  74. data/test/tools/test.rb +0 -4
  75. data/test/unit/repl_set_connection_test.rb +0 -59
@@ -24,18 +24,18 @@ module Mongo
24
24
 
25
25
  # Instantiates and manages connections to MongoDB.
26
26
  class Connection
27
+ include Mongo::Logging
28
+
27
29
  TCPSocket = ::TCPSocket
28
30
  Mutex = ::Mutex
29
31
  ConditionVariable = ::ConditionVariable
30
32
 
31
- # Abort connections if a ConnectionError is raised.
32
- Thread.abort_on_exception = true
33
-
34
33
  DEFAULT_PORT = 27017
35
34
  STANDARD_HEADER_SIZE = 16
36
35
  RESPONSE_HEADER_SIZE = 20
37
36
 
38
- attr_reader :logger, :size, :auths, :primary, :safe, :primary_pool, :host_to_try, :pool_size
37
+ attr_reader :logger, :size, :auths, :primary, :safe, :host_to_try,
38
+ :pool_size, :connect_timeout, :primary_pool, :socket_class
39
39
 
40
40
  # Counter for generating unique request ids.
41
41
  @@current_request_id = 0
@@ -65,11 +65,14 @@ module Mongo
65
65
  # logging negatively impacts performance; therefore, it should not be used for high-performance apps.
66
66
  # @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
67
67
  # connection pool. Note: this setting is relevant only for multi-threaded applications.
68
- # @option opts [Float] :timeout (5.0) When all of the connections a pool are checked out,
68
+ # @option opts [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
69
69
  # this is the number of seconds to wait for a new connection to be released before throwing an exception.
70
70
  # Note: this setting is relevant only for multi-threaded applications (which in Ruby are rare).
71
71
  # @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
72
72
  # Disabled by default.
73
+ # @option opts [Float] :connect_timeout (nil) The number of seconds to wait before timing out a
74
+ # connection attempt.
75
+ # @option opts [Boolean] :ssl (false) If true, create the connection to the server using SSL.
73
76
  #
74
77
  # @example localhost, 27017
75
78
  # Connection.new
@@ -128,7 +131,7 @@ module Mongo
128
131
  #
129
132
  # @deprecated
130
133
  def self.multi(nodes, opts={})
131
- warn "Connection.multi is now deprecated. Please use ReplSetConnection.new instead."
134
+ warn "Connection.multi is now deprecated and will be removed in v2.0. Please use ReplSetConnection.new instead."
132
135
 
133
136
  nodes << opts
134
137
  ReplSetConnection.new(*nodes)
@@ -306,7 +309,7 @@ module Mongo
306
309
  #
307
310
  # @core databases []-instance_method
308
311
  def [](db_name)
309
- DB.new(db_name, self, :safe => @safe)
312
+ DB.new(db_name, self)
310
313
  end
311
314
 
312
315
  # Drop a database.
@@ -406,11 +409,7 @@ module Mongo
406
409
 
407
410
  send_message_on_socket(packed_message, socket)
408
411
  ensure
409
- if connection == :writer
410
- checkin_writer(socket)
411
- else
412
- checkin_reader(socket)
413
- end
412
+ checkin(socket)
414
413
  end
415
414
  end
416
415
 
@@ -442,13 +441,13 @@ module Mongo
442
441
  docs, num_received, cursor_id = receive(sock, last_error_id)
443
442
  end
444
443
  ensure
445
- checkin_writer(sock)
444
+ checkin(sock)
446
445
  end
447
446
 
448
447
  if num_received == 1 && (error = docs[0]['err'] || docs[0]['errmsg'])
449
448
  close if error == "not master"
450
449
  error = "wtimeout" if error == "timeout"
451
- raise Mongo::OperationFailure, docs[0]['code'].to_s + ': ' + error
450
+ raise OperationFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
452
451
  end
453
452
 
454
453
  docs[0]
@@ -462,30 +461,40 @@ module Mongo
462
461
  # @param [Socket] socket a socket to use in lieu of checking out a new one.
463
462
  # @param [Boolean] command (false) indicate whether this is a command. If this is a command,
464
463
  # the message will be sent to the primary node.
464
+ # @param [Boolean] command (false) indicate whether the cursor should be exhausted. Set
465
+ # this to true only when the OP_QUERY_EXHAUST flag is set.
465
466
  #
466
467
  # @return [Array]
467
468
  # An array whose indexes include [0] documents returned, [1] number of document received,
468
469
  # and [3] a cursor_id.
469
- def receive_message(operation, message, log_message=nil, socket=nil, command=false)
470
+ def receive_message(operation, message, log_message=nil, socket=nil, command=false, read=:primary, exhaust=false)
470
471
  request_id = add_message_headers(message, operation)
471
472
  packed_message = message.to_s
472
473
  begin
473
474
  if socket
474
475
  sock = socket
475
- checkin = false
476
+ should_checkin = false
476
477
  else
477
- sock = (command ? checkout_writer : checkout_reader)
478
- checkin = true
478
+ if command
479
+ sock = checkout_writer
480
+ elsif read == :primary
481
+ sock = checkout_writer
482
+ elsif read == :secondary
483
+ sock = checkout_reader
484
+ else
485
+ sock = checkout_tagged(read)
486
+ end
487
+ should_checkin = true
479
488
  end
480
489
 
481
490
  result = ''
482
491
  @safe_mutexes[sock].synchronize do
483
492
  send_message_on_socket(packed_message, sock)
484
- result = receive(sock, request_id)
493
+ result = receive(sock, request_id, exhaust)
485
494
  end
486
495
  ensure
487
- if checkin
488
- command ? checkin_writer(sock) : checkin_reader(sock)
496
+ if should_checkin
497
+ checkin(sock)
489
498
  end
490
499
  end
491
500
  result
@@ -509,12 +518,11 @@ module Mongo
509
518
  @read_primary = false
510
519
  end
511
520
 
521
+ @max_bson_size = config['maxBsonObjectSize'] || Mongo::DEFAULT_MAX_BSON_SIZE
512
522
  set_primary(@host_to_try)
513
523
  end
514
524
 
515
- if connected?
516
- BSON::BSON_CODER.update_max_bson_size(self)
517
- else
525
+ if !connected?
518
526
  raise ConnectionFailure, "Failed to connect to a master node at #{@host_to_try[0]}:#{@host_to_try[1]}"
519
527
  end
520
528
  end
@@ -556,6 +564,14 @@ module Mongo
556
564
  end
557
565
  alias :primary? :read_primary?
558
566
 
567
+ # The value of the read preference. Because
568
+ # this is a single-node connection, the value
569
+ # is +:primary+, and the connection will read
570
+ # from whichever type of node it's connected to.
571
+ def read_preference
572
+ :primary
573
+ end
574
+
559
575
  # Close the connection to the database.
560
576
  def close
561
577
  @primary_pool.close if @primary_pool
@@ -568,8 +584,7 @@ module Mongo
568
584
  #
569
585
  # @return [Integer]
570
586
  def max_bson_size
571
- config = self['admin'].command({:ismaster => 1})
572
- config['maxBsonObjectSize'] || Mongo::DEFAULT_MAX_BSON_SIZE
587
+ @max_bson_size
573
588
  end
574
589
 
575
590
  # Checkout a socket for reading (i.e., a secondary node).
@@ -589,32 +604,41 @@ module Mongo
589
604
  # Checkin a socket used for reading.
590
605
  # Note: this is overridden in ReplSetConnection.
591
606
  def checkin_reader(socket)
592
- if @primary_pool
593
- @primary_pool.checkin(socket)
594
- end
607
+ warn "Connection#checkin_writer is not deprecated and will be removed " +
608
+ "in driver v2.0. Use Connection#checkin instead."
609
+ checkin(socket)
595
610
  end
596
611
 
597
612
  # Checkin a socket used for writing.
598
613
  # Note: this is overridden in ReplSetConnection.
599
614
  def checkin_writer(socket)
615
+ warn "Connection#checkin_writer is not deprecated and will be removed " +
616
+ "in driver v2.0. Use Connection#checkin instead."
617
+ checkin(socket)
618
+ end
619
+
620
+ # Check a socket back into its pool.
621
+ def checkin(socket)
600
622
  if @primary_pool
601
623
  @primary_pool.checkin(socket)
602
624
  end
603
625
  end
604
626
 
605
- # Execute the block and log the operation described by name
606
- # and payload.
607
- # TODO: Not sure if this should take a block.
608
- def instrument(name, payload = {}, &blk)
609
- res = yield
610
- log_operation(name, payload)
611
- res
612
- end
613
-
614
627
  protected
615
628
 
616
629
  # Generic initialization code.
617
630
  def setup(opts)
631
+ # Default maximum BSON object size
632
+ @max_bson_size = Mongo::DEFAULT_MAX_BSON_SIZE
633
+
634
+ # Determine whether to use SSL.
635
+ @ssl = opts.fetch(:ssl, false)
636
+ if @ssl
637
+ @socket_class = Mongo::SSLSocket
638
+ else
639
+ @socket_class = ::TCPSocket
640
+ end
641
+
618
642
  # Authentication objects
619
643
  @auths = opts.fetch(:auths, [])
620
644
 
@@ -623,18 +647,26 @@ module Mongo
623
647
 
624
648
  # Pool size and timeout.
625
649
  @pool_size = opts[:pool_size] || 1
626
- @timeout = opts[:timeout] || 5.0
650
+ if opts[:timeout]
651
+ warn "The :timeout option has been deprecated " +
652
+ "and will be removed in the 2.0 release. Use :pool_timeout instead."
653
+ end
654
+ @timeout = opts[:pool_timeout] || opts[:timeout] || 5.0
627
655
 
628
656
  # Timeout on socket read operation.
629
657
  @op_timeout = opts[:op_timeout] || nil
630
658
 
659
+ # Timeout on socket connect.
660
+ @connect_timeout = opts[:connect_timeout] || nil
661
+
631
662
  # Mutex for synchronizing pool access
663
+ # TODO: remove this.
632
664
  @connection_mutex = Mutex.new
633
665
 
634
666
  # Global safe option. This is false by default.
635
667
  @safe = opts[:safe] || false
636
668
 
637
- # Create a mutex when a new key, in this case a socket,
669
+ # Create a mutex when a new key, in this case a socket,
638
670
  # is added to the hash.
639
671
  @safe_mutexes = Hash.new { |h, k| h[k] = Mutex.new }
640
672
 
@@ -672,18 +704,6 @@ module Mongo
672
704
  end
673
705
  end
674
706
 
675
- ## Logging methods
676
-
677
- def log_operation(name, payload)
678
- return unless @logger
679
- msg = "#{payload[:database]}['#{payload[:collection]}'].#{name}("
680
- msg += payload.values_at(:selector, :document, :documents, :fields ).compact.map(&:inspect).join(', ') + ")"
681
- msg += ".skip(#{payload[:skip]})" if payload[:skip]
682
- msg += ".limit(#{payload[:limit]})" if payload[:limit]
683
- msg += ".sort(#{payload[:order]})" if payload[:order]
684
- @logger.debug "MONGODB #{msg}"
685
- end
686
-
687
707
  private
688
708
 
689
709
  ## Methods for establishing a connection:
@@ -698,8 +718,16 @@ module Mongo
698
718
  def check_is_master(node)
699
719
  begin
700
720
  host, port = *node
701
- socket = TCPSocket.new(host, port)
702
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
721
+
722
+ if @connect_timeout
723
+ Mongo::TimeoutHandler.timeout(@connect_timeout, OperationTimeout) do
724
+ socket = @socket_class.new(host, port)
725
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
726
+ end
727
+ else
728
+ socket = @socket_class.new(host, port)
729
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
730
+ end
703
731
 
704
732
  config = self['admin'].command({:ismaster => 1}, :socket => socket)
705
733
  rescue OperationFailure, SocketError, SystemCallError, IOError => ex
@@ -720,21 +748,38 @@ module Mongo
720
748
 
721
749
  ## Low-level connection methods.
722
750
 
723
- def receive(sock, expected_response)
751
+ def receive(sock, cursor_id, exhaust=false)
724
752
  begin
725
- receive_header(sock, expected_response)
726
- number_received, cursor_id = receive_response_header(sock)
727
- read_documents(number_received, cursor_id, sock)
753
+ if exhaust
754
+ docs = []
755
+ num_received = 0
756
+
757
+ while(cursor_id != 0) do
758
+ receive_header(sock, cursor_id, exhaust)
759
+ number_received, cursor_id = receive_response_header(sock)
760
+ new_docs, n = read_documents(number_received, sock)
761
+ docs += new_docs
762
+ num_received += n
763
+ end
764
+
765
+ return [docs, num_received, cursor_id]
766
+ else
767
+ receive_header(sock, cursor_id, exhaust)
768
+ number_received, cursor_id = receive_response_header(sock)
769
+ docs, num_received = read_documents(number_received, sock)
770
+
771
+ return [docs, num_received, cursor_id]
772
+ end
728
773
  rescue Mongo::ConnectionFailure => ex
729
774
  close
730
775
  raise ex
731
776
  end
732
777
  end
733
778
 
734
- def receive_header(sock, expected_response)
779
+ def receive_header(sock, expected_response, exhaust=false)
735
780
  header = receive_message_on_socket(16, sock)
736
781
  size, request_id, response_to = header.unpack('VVV')
737
- if expected_response != response_to
782
+ if !exhaust && expected_response != response_to
738
783
  raise Mongo::ConnectionFailure, "Expected response #{expected_response} but got #{response_to}"
739
784
  end
740
785
 
@@ -766,7 +811,7 @@ module Mongo
766
811
  end
767
812
  end
768
813
 
769
- def read_documents(number_received, cursor_id, sock)
814
+ def read_documents(number_received, sock)
770
815
  docs = []
771
816
  number_remaining = number_received
772
817
  while number_remaining > 0 do
@@ -776,7 +821,7 @@ module Mongo
776
821
  number_remaining -= 1
777
822
  docs << BSON::BSON_CODER.deserialize(buf)
778
823
  end
779
- [docs, number_received, cursor_id]
824
+ [docs, number_received]
780
825
  end
781
826
 
782
827
  # Constructs a getlasterror message. This method is used exclusively by
@@ -18,12 +18,15 @@ module Mongo
18
18
 
19
19
  # A cursor over query results. Returned objects are hashes.
20
20
  class Cursor
21
- include Mongo::Conversions
22
21
  include Enumerable
22
+ include Mongo::Constants
23
+ include Mongo::Conversions
24
+ include Mongo::Logging
23
25
 
24
26
  attr_reader :collection, :selector, :fields,
25
27
  :order, :hint, :snapshot, :timeout,
26
- :full_collection_name, :transformer
28
+ :full_collection_name, :transformer,
29
+ :options, :cursor_id
27
30
 
28
31
  # Create a new cursor.
29
32
  #
@@ -59,6 +62,7 @@ module Mongo
59
62
  @limit = opts[:limit] || 0
60
63
  @tailable = opts[:tailable] || false
61
64
  @timeout = opts.fetch(:timeout, true)
65
+ @options = 0
62
66
 
63
67
  # Use this socket for the query
64
68
  @socket = opts[:socket]
@@ -67,12 +71,28 @@ module Mongo
67
71
  @query_run = false
68
72
 
69
73
  @transformer = opts[:transformer]
74
+ if value = opts[:read]
75
+ Mongo::Support.validate_read_preference(value)
76
+ else
77
+ value = collection.read_preference
78
+ end
79
+ @read_preference = value.is_a?(Hash) ? value.dup : value
70
80
  batch_size(opts[:batch_size] || 0)
71
81
 
72
82
  @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
73
83
  @cache = []
74
84
  @returned = 0
75
85
 
86
+ if(!@timeout)
87
+ add_option(OP_QUERY_NO_CURSOR_TIMEOUT)
88
+ end
89
+ if(@connection.slave_ok?)
90
+ add_option(OP_QUERY_SLAVE_OK)
91
+ end
92
+ if(@tailable)
93
+ add_option(OP_QUERY_TAILABLE)
94
+ end
95
+
76
96
  if @collection.name =~ /^\$cmd/ || @collection.name =~ /^system/
77
97
  @command = true
78
98
  else
@@ -80,11 +100,30 @@ module Mongo
80
100
  end
81
101
  end
82
102
 
103
+ # Guess whether the cursor is alive on the server.
104
+ #
105
+ # Note that this method only checks whether we have
106
+ # a cursor id. The cursor may still have timed out
107
+ # on the server. This will be indicated in the next
108
+ # call to Cursor#next.
109
+ #
110
+ # @return [Boolean]
111
+ def alive?
112
+ @cursor_id && @cursor_id != 0
113
+ end
114
+
83
115
  # Get the next document specified the cursor options.
84
116
  #
85
117
  # @return [Hash, Nil] the next document or Nil if no documents remain.
86
- def next_document
87
- refresh if @cache.length == 0
118
+ def next
119
+ if @cache.length == 0
120
+ if @query_run && (@options & OP_QUERY_EXHAUST != 0)
121
+ close
122
+ return nil
123
+ else
124
+ refresh
125
+ end
126
+ end
88
127
  doc = @cache.shift
89
128
 
90
129
  if doc && doc['$err']
@@ -95,10 +134,10 @@ module Mongo
95
134
  # connection. The next request will re-open on master server.
96
135
  if err == "not master"
97
136
  @connection.close
98
- raise ConnectionFailure, err
137
+ raise ConnectionFailure.new(err, doc['code'], doc)
99
138
  end
100
139
 
101
- raise OperationFailure, err
140
+ raise OperationFailure.new(err, doc['code'], doc)
102
141
  end
103
142
 
104
143
  if @transformer.nil?
@@ -107,7 +146,7 @@ module Mongo
107
146
  @transformer.call(doc) if doc
108
147
  end
109
148
  end
110
- alias :next :next_document
149
+ alias :next_document :next
111
150
 
112
151
  # Reset this cursor on the server. Cursor options, such as the
113
152
  # query string and the values for skip and limit, are preserved.
@@ -148,7 +187,7 @@ module Mongo
148
187
  response = @db.command(command)
149
188
  return response['n'].to_i if Mongo::Support.ok?(response)
150
189
  return 0 if response['errmsg'] == "ns missing"
151
- raise OperationFailure, "Count failed: #{response['errmsg']}"
190
+ raise OperationFailure.new("Count failed: #{response['errmsg']}", response['code'], response)
152
191
  end
153
192
 
154
193
  # Sort this cursor's results.
@@ -220,12 +259,13 @@ module Mongo
220
259
  # the server will determine the batch size.
221
260
  #
222
261
  # @return [Cursor]
223
- def batch_size(size=0)
262
+ def batch_size(size=nil)
263
+ return @batch_size unless size
224
264
  check_modifiable
225
265
  if size < 0 || size == 1
226
266
  raise ArgumentError, "Invalid value for batch_size #{size}; must be 0 or > 1."
227
267
  else
228
- @batch_size = size > @limit ? @limit : size
268
+ @batch_size = @limit != 0 && size > @limit ? @limit : size
229
269
  end
230
270
 
231
271
  self
@@ -243,11 +283,8 @@ module Mongo
243
283
  # puts doc['user']
244
284
  # end
245
285
  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
286
+ while doc = self.next
287
+ yield doc
251
288
  end
252
289
  end
253
290
 
@@ -273,7 +310,8 @@ module Mongo
273
310
  #
274
311
  # @core explain explain-instance_method
275
312
  def explain
276
- c = Cursor.new(@collection, query_options_hash.merge(:limit => -@limit.abs, :explain => true))
313
+ c = Cursor.new(@collection,
314
+ query_options_hash.merge(:limit => -@limit.abs, :explain => true))
277
315
  explanation = c.next_document
278
316
  c.close
279
317
 
@@ -295,7 +333,7 @@ module Mongo
295
333
  message = BSON::ByteBuffer.new([0, 0, 0, 0])
296
334
  message.put_int(1)
297
335
  message.put_long(@cursor_id)
298
- @logger.debug("MONGODB cursor.close #{@cursor_id}") if @logger
336
+ log(:debug, "Cursor#close #{@cursor_id}")
299
337
  @connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, :connection => :reader)
300
338
  end
301
339
  @cursor_id = 0
@@ -305,7 +343,9 @@ module Mongo
305
343
  # Is this cursor closed?
306
344
  #
307
345
  # @return [Boolean]
308
- def closed?; @closed; end
346
+ def closed?
347
+ @closed
348
+ end
309
349
 
310
350
  # Returns an integer indicating which query options have been selected.
311
351
  #
@@ -314,11 +354,43 @@ module Mongo
314
354
  # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
315
355
  # The MongoDB wire protocol.
316
356
  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
357
+ warn "The method Cursor#query_opts has been deprecated " +
358
+ "and will removed in v2.0. Use Cursor#options instead."
359
+ @options
360
+ end
361
+
362
+ # Add an option to the query options bitfield.
363
+ #
364
+ # @param opt a valid query option
365
+ #
366
+ # @raise InvalidOperation if this method is run after the cursor has bee
367
+ # iterated for the first time.
368
+ #
369
+ # @return [Integer] the current value of the options bitfield for this cursor.
370
+ #
371
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
372
+ def add_option(opt)
373
+ check_modifiable
374
+
375
+ @options |= opt
376
+ @options
377
+ end
378
+
379
+ # Remove an option from the query options bitfield.
380
+ #
381
+ # @param opt a valid query option
382
+ #
383
+ # @raise InvalidOperation if this method is run after the cursor has bee
384
+ # iterated for the first time.
385
+ #
386
+ # @return [Integer] the current value of the options bitfield for this cursor.
387
+ #
388
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
389
+ def remove_option(opt)
390
+ check_modifiable
391
+
392
+ @options &= ~opt
393
+ @options
322
394
  end
323
395
 
324
396
  # Get the query options for this Cursor.
@@ -341,7 +413,7 @@ module Mongo
341
413
  # Clean output for inspect.
342
414
  def inspect
343
415
  "<Mongo::Cursor:0x#{object_id.to_s(16)} namespace='#{@db.name}.#{@collection.name}' " +
344
- "@selector=#{@selector.inspect}>"
416
+ "@selector=#{@selector.inspect} @cursor_id=#{@cursor_id}>"
345
417
  end
346
418
 
347
419
  private
@@ -363,12 +435,43 @@ module Mongo
363
435
 
364
436
  # Return the number of documents remaining for this cursor.
365
437
  def num_remaining
366
- refresh if @cache.length == 0
438
+ if @cache.length == 0
439
+ if @query_run && (@options & OP_QUERY_EXHAUST != 0)
440
+ close
441
+ return 0
442
+ else
443
+ refresh
444
+ end
445
+ end
446
+
367
447
  @cache.length
368
448
  end
369
449
 
450
+ # Refresh the documents in @cache. This means either
451
+ # sending the initial query or sending a GET_MORE operation.
370
452
  def refresh
371
- return if send_initial_query || @cursor_id.zero?
453
+ if !@query_run
454
+ send_initial_query
455
+ elsif !@cursor_id.zero?
456
+ send_get_more
457
+ end
458
+ end
459
+
460
+ def send_initial_query
461
+ message = construct_query_message
462
+ payload = instrument_payload if @logger
463
+ instrument(:find, payload) do
464
+ results, @n_received, @cursor_id = @connection.receive_message(
465
+ Mongo::Constants::OP_QUERY, message, nil, @socket, @command,
466
+ @read_preference, @options & OP_QUERY_EXHAUST != 0)
467
+ @returned += @n_received
468
+ @cache += results
469
+ @query_run = true
470
+ close_cursor_if_query_complete
471
+ end
472
+ end
473
+
474
+ def send_get_more
372
475
  message = BSON::ByteBuffer.new([0, 0, 0, 0])
373
476
 
374
477
  # DB name.
@@ -387,37 +490,17 @@ module Mongo
387
490
 
388
491
  # Cursor id.
389
492
  message.put_long(@cursor_id)
390
- @logger.debug("MONGODB cursor.refresh() for cursor #{@cursor_id}") if @logger
493
+ log(:debug, "cursor.refresh() for cursor #{@cursor_id}") if @logger
391
494
  results, @n_received, @cursor_id = @connection.receive_message(
392
- Mongo::Constants::OP_GET_MORE, message, nil, @socket, @command)
495
+ Mongo::Constants::OP_GET_MORE, message, nil, @socket, @command, @read_preference)
393
496
  @returned += @n_received
394
497
  @cache += results
395
498
  close_cursor_if_query_complete
396
499
  end
397
500
 
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
501
  def construct_query_message
419
502
  message = BSON::ByteBuffer.new
420
- message.put_int(query_opts)
503
+ message.put_int(@options)
421
504
  BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
422
505
  message.put_int(@skip)
423
506
  message.put_int(@limit)
@@ -429,10 +512,10 @@ module Mongo
429
512
 
430
513
  def instrument_payload
431
514
  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
515
+ log[:fields] = @fields if @fields
516
+ log[:skip] = @skip if @skip && (@skip != 0)
517
+ log[:limit] = @limit if @limit && (@limit != 0)
518
+ log[:order] = @order if @order
436
519
  log
437
520
  end
438
521
 
@@ -455,10 +538,6 @@ module Mongo
455
538
  @order || @explain || @hint || @snapshot
456
539
  end
457
540
 
458
- def to_s
459
- "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)"
460
- end
461
-
462
541
  def close_cursor_if_query_complete
463
542
  if @limit > 0 && @returned >= @limit
464
543
  close