mongo 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +9 -6
- data/Rakefile +3 -4
- data/docs/HISTORY.md +20 -2
- data/docs/READ_PREFERENCE.md +39 -0
- data/docs/RELEASES.md +1 -1
- data/docs/REPLICA_SETS.md +23 -2
- data/docs/TAILABLE_CURSORS.md +51 -0
- data/docs/TUTORIAL.md +4 -4
- data/docs/WRITE_CONCERN.md +5 -2
- data/lib/mongo.rb +7 -22
- data/lib/mongo/collection.rb +96 -29
- data/lib/mongo/connection.rb +107 -62
- data/lib/mongo/cursor.rb +136 -57
- data/lib/mongo/db.rb +26 -5
- data/lib/mongo/exceptions.rb +17 -1
- data/lib/mongo/gridfs/grid.rb +1 -1
- data/lib/mongo/repl_set_connection.rb +273 -156
- data/lib/mongo/util/logging.rb +42 -0
- data/lib/mongo/util/node.rb +183 -0
- data/lib/mongo/util/pool.rb +76 -13
- data/lib/mongo/util/pool_manager.rb +208 -0
- data/lib/mongo/util/ssl_socket.rb +38 -0
- data/lib/mongo/util/support.rb +9 -1
- data/lib/mongo/util/timeout.rb +42 -0
- data/lib/mongo/version.rb +3 -0
- data/mongo.gemspec +2 -2
- data/test/bson/binary_test.rb +1 -1
- data/test/bson/bson_string_test.rb +30 -0
- data/test/bson/bson_test.rb +6 -3
- data/test/bson/byte_buffer_test.rb +1 -1
- data/test/bson/hash_with_indifferent_access_test.rb +1 -1
- data/test/bson/json_test.rb +1 -1
- data/test/bson/object_id_test.rb +2 -18
- data/test/bson/ordered_hash_test.rb +38 -3
- data/test/bson/test_helper.rb +46 -0
- data/test/bson/timestamp_test.rb +32 -10
- data/test/collection_test.rb +89 -3
- data/test/connection_test.rb +35 -20
- data/test/cursor_test.rb +63 -2
- data/test/db_test.rb +12 -2
- data/test/pool_test.rb +21 -0
- data/test/replica_sets/connect_test.rb +26 -13
- data/test/replica_sets/connection_string_test.rb +1 -4
- data/test/replica_sets/count_test.rb +1 -0
- data/test/replica_sets/insert_test.rb +1 -0
- data/test/replica_sets/pooled_insert_test.rb +4 -1
- data/test/replica_sets/query_secondaries.rb +2 -1
- data/test/replica_sets/query_test.rb +2 -1
- data/test/replica_sets/read_preference_test.rb +43 -0
- data/test/replica_sets/refresh_test.rb +123 -0
- data/test/replica_sets/replication_ack_test.rb +9 -4
- data/test/replica_sets/rs_test_helper.rb +2 -2
- data/test/timeout_test.rb +14 -0
- data/test/tools/repl_set_manager.rb +134 -23
- data/test/unit/collection_test.rb +6 -8
- data/test/unit/connection_test.rb +4 -4
- data/test/unit/cursor_test.rb +23 -5
- data/test/unit/db_test.rb +2 -0
- data/test/unit/grid_test.rb +2 -0
- data/test/unit/node_test.rb +73 -0
- data/test/unit/pool_manager_test.rb +47 -0
- data/test/unit/read_test.rb +101 -0
- metadata +214 -138
- data/lib/mongo/test.rb +0 -20
- data/test/async/collection_test.rb +0 -224
- data/test/async/connection_test.rb +0 -24
- data/test/async/cursor_test.rb +0 -162
- data/test/async/worker_pool_test.rb +0 -99
- data/test/load/resque/load.rb +0 -21
- data/test/load/resque/processor.rb +0 -26
- data/test/load/unicorn/unicorn.rb +0 -29
- data/test/tools/load.rb +0 -58
- data/test/tools/sharding_manager.rb +0 -202
- data/test/tools/test.rb +0 -4
- data/test/unit/repl_set_connection_test.rb +0 -59
data/lib/mongo/connection.rb
CHANGED
@@ -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, :
|
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] :
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
476
|
+
should_checkin = false
|
476
477
|
else
|
477
|
-
|
478
|
-
|
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
|
488
|
-
|
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
|
-
|
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
|
-
|
593
|
-
|
594
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
702
|
-
|
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,
|
751
|
+
def receive(sock, cursor_id, exhaust=false)
|
724
752
|
begin
|
725
|
-
|
726
|
-
|
727
|
-
|
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,
|
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
|
824
|
+
[docs, number_received]
|
780
825
|
end
|
781
826
|
|
782
827
|
# Constructs a getlasterror message. This method is used exclusively by
|
data/lib/mongo/cursor.rb
CHANGED
@@ -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
|
87
|
-
|
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,
|
137
|
+
raise ConnectionFailure.new(err, doc['code'], doc)
|
99
138
|
end
|
100
139
|
|
101
|
-
raise OperationFailure,
|
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 :
|
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
|
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=
|
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
|
-
|
247
|
-
|
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,
|
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
|
-
|
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
|
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
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
433
|
-
log[:skip]
|
434
|
-
log[:limit]
|
435
|
-
log[: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
|