mongo 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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