mongo 1.10.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +190 -0
  5. data/README.md +149 -0
  6. data/Rakefile +31 -0
  7. data/VERSION +1 -0
  8. data/bin/mongo_console +43 -0
  9. data/ext/jsasl/target/jsasl.jar +0 -0
  10. data/lib/mongo.rb +90 -0
  11. data/lib/mongo/bulk_write_collection_view.rb +380 -0
  12. data/lib/mongo/collection.rb +1164 -0
  13. data/lib/mongo/collection_writer.rb +364 -0
  14. data/lib/mongo/connection.rb +19 -0
  15. data/lib/mongo/connection/node.rb +239 -0
  16. data/lib/mongo/connection/pool.rb +347 -0
  17. data/lib/mongo/connection/pool_manager.rb +325 -0
  18. data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
  19. data/lib/mongo/connection/socket.rb +18 -0
  20. data/lib/mongo/connection/socket/socket_util.rb +37 -0
  21. data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
  22. data/lib/mongo/connection/socket/tcp_socket.rb +86 -0
  23. data/lib/mongo/connection/socket/unix_socket.rb +39 -0
  24. data/lib/mongo/cursor.rb +719 -0
  25. data/lib/mongo/db.rb +735 -0
  26. data/lib/mongo/exception.rb +88 -0
  27. data/lib/mongo/functional.rb +21 -0
  28. data/lib/mongo/functional/authentication.rb +318 -0
  29. data/lib/mongo/functional/logging.rb +85 -0
  30. data/lib/mongo/functional/read_preference.rb +174 -0
  31. data/lib/mongo/functional/sasl_java.rb +48 -0
  32. data/lib/mongo/functional/uri_parser.rb +374 -0
  33. data/lib/mongo/functional/write_concern.rb +66 -0
  34. data/lib/mongo/gridfs.rb +18 -0
  35. data/lib/mongo/gridfs/grid.rb +112 -0
  36. data/lib/mongo/gridfs/grid_ext.rb +53 -0
  37. data/lib/mongo/gridfs/grid_file_system.rb +163 -0
  38. data/lib/mongo/gridfs/grid_io.rb +484 -0
  39. data/lib/mongo/legacy.rb +140 -0
  40. data/lib/mongo/mongo_client.rb +702 -0
  41. data/lib/mongo/mongo_replica_set_client.rb +523 -0
  42. data/lib/mongo/mongo_sharded_client.rb +159 -0
  43. data/lib/mongo/networking.rb +370 -0
  44. data/lib/mongo/utils.rb +19 -0
  45. data/lib/mongo/utils/conversions.rb +110 -0
  46. data/lib/mongo/utils/core_ext.rb +70 -0
  47. data/lib/mongo/utils/server_version.rb +69 -0
  48. data/lib/mongo/utils/support.rb +80 -0
  49. data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
  50. data/mongo.gemspec +36 -0
  51. data/test/functional/authentication_test.rb +35 -0
  52. data/test/functional/bulk_api_stress_test.rb +133 -0
  53. data/test/functional/bulk_write_collection_view_test.rb +1129 -0
  54. data/test/functional/client_test.rb +565 -0
  55. data/test/functional/collection_test.rb +2073 -0
  56. data/test/functional/collection_writer_test.rb +83 -0
  57. data/test/functional/conversions_test.rb +163 -0
  58. data/test/functional/cursor_fail_test.rb +63 -0
  59. data/test/functional/cursor_message_test.rb +57 -0
  60. data/test/functional/cursor_test.rb +625 -0
  61. data/test/functional/db_api_test.rb +819 -0
  62. data/test/functional/db_connection_test.rb +27 -0
  63. data/test/functional/db_test.rb +344 -0
  64. data/test/functional/grid_file_system_test.rb +285 -0
  65. data/test/functional/grid_io_test.rb +252 -0
  66. data/test/functional/grid_test.rb +273 -0
  67. data/test/functional/pool_test.rb +62 -0
  68. data/test/functional/safe_test.rb +98 -0
  69. data/test/functional/ssl_test.rb +29 -0
  70. data/test/functional/support_test.rb +62 -0
  71. data/test/functional/timeout_test.rb +58 -0
  72. data/test/functional/uri_test.rb +330 -0
  73. data/test/functional/write_concern_test.rb +118 -0
  74. data/test/helpers/general.rb +50 -0
  75. data/test/helpers/test_unit.rb +317 -0
  76. data/test/replica_set/authentication_test.rb +35 -0
  77. data/test/replica_set/basic_test.rb +174 -0
  78. data/test/replica_set/client_test.rb +341 -0
  79. data/test/replica_set/complex_connect_test.rb +77 -0
  80. data/test/replica_set/connection_test.rb +138 -0
  81. data/test/replica_set/count_test.rb +64 -0
  82. data/test/replica_set/cursor_test.rb +212 -0
  83. data/test/replica_set/insert_test.rb +140 -0
  84. data/test/replica_set/max_values_test.rb +145 -0
  85. data/test/replica_set/pinning_test.rb +55 -0
  86. data/test/replica_set/query_test.rb +73 -0
  87. data/test/replica_set/read_preference_test.rb +214 -0
  88. data/test/replica_set/refresh_test.rb +175 -0
  89. data/test/replica_set/replication_ack_test.rb +94 -0
  90. data/test/replica_set/ssl_test.rb +32 -0
  91. data/test/sharded_cluster/basic_test.rb +197 -0
  92. data/test/shared/authentication/basic_auth_shared.rb +286 -0
  93. data/test/shared/authentication/bulk_api_auth_shared.rb +259 -0
  94. data/test/shared/authentication/gssapi_shared.rb +164 -0
  95. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  96. data/test/shared/ssl_shared.rb +235 -0
  97. data/test/test_helper.rb +56 -0
  98. data/test/threading/basic_test.rb +120 -0
  99. data/test/tools/mongo_config.rb +608 -0
  100. data/test/tools/mongo_config_test.rb +160 -0
  101. data/test/unit/client_test.rb +347 -0
  102. data/test/unit/collection_test.rb +166 -0
  103. data/test/unit/connection_test.rb +325 -0
  104. data/test/unit/cursor_test.rb +299 -0
  105. data/test/unit/db_test.rb +136 -0
  106. data/test/unit/grid_test.rb +76 -0
  107. data/test/unit/mongo_sharded_client_test.rb +48 -0
  108. data/test/unit/node_test.rb +93 -0
  109. data/test/unit/pool_manager_test.rb +142 -0
  110. data/test/unit/read_pref_test.rb +115 -0
  111. data/test/unit/read_test.rb +159 -0
  112. data/test/unit/safe_test.rb +158 -0
  113. data/test/unit/sharding_pool_manager_test.rb +84 -0
  114. data/test/unit/write_concern_test.rb +175 -0
  115. metadata +260 -0
  116. metadata.gz.sig +0 -0
@@ -0,0 +1,67 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ class ShardingPoolManager < PoolManager
17
+ def inspect
18
+ "<Mongo::ShardingPoolManager:0x#{self.object_id.to_s(16)} @seeds=#{@seeds}>"
19
+ end
20
+
21
+ # "Best" should be the member with the fastest ping time
22
+ # but connect/connect_to_members reinitializes @members
23
+ def best(members)
24
+ Array(members.first)
25
+ end
26
+
27
+ def connect
28
+ @connect_mutex.synchronize do
29
+ begin
30
+ thread_local[:locks][:connecting_manager] = true
31
+ @refresh_required = false
32
+ disconnect_old_members
33
+ connect_to_members
34
+ initialize_pools best(@members)
35
+ update_max_sizes
36
+ @seeds = discovered_seeds
37
+ ensure
38
+ thread_local[:locks][:connecting_manager] = false
39
+ end
40
+ end
41
+ end
42
+
43
+ # Checks that each node is healthy (via check_is_master) and that each
44
+ # node is in fact a mongos. If either criteria are not true, a refresh is
45
+ # set to be triggered and close() is called on the node.
46
+ #
47
+ # @return [Boolean] indicating if a refresh is required.
48
+ def check_connection_health
49
+ @refresh_required = false
50
+ @members.each do |member|
51
+ begin
52
+ config = @client.check_is_master([member.host, member.port])
53
+ unless config && config.has_key?('msg')
54
+ @refresh_required = true
55
+ member.close
56
+ end
57
+ rescue OperationTimeout
58
+ @refresh_required = true
59
+ member.close
60
+ end
61
+ break if @refresh_required
62
+ end
63
+ @refresh_required
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,18 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'mongo/connection/socket/socket_util.rb'
16
+ require 'mongo/connection/socket/ssl_socket.rb'
17
+ require 'mongo/connection/socket/tcp_socket.rb'
18
+ require 'mongo/connection/socket/unix_socket.rb'
@@ -0,0 +1,37 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'socket'
16
+ require 'timeout'
17
+
18
+ module SocketUtil
19
+
20
+ attr_accessor :pool, :pid, :auths
21
+
22
+ def checkout
23
+ @pool.checkout if @pool
24
+ end
25
+
26
+ def checkin
27
+ @pool.checkin(self) if @pool
28
+ end
29
+
30
+ def close
31
+ @socket.close unless closed?
32
+ end
33
+
34
+ def closed?
35
+ @socket.closed?
36
+ end
37
+ end
@@ -0,0 +1,95 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'openssl'
16
+
17
+ module Mongo
18
+
19
+ # A basic wrapper over Ruby's SSLSocket that initiates
20
+ # a TCP connection over SSL and then provides an basic interface
21
+ # mirroring Ruby's TCPSocket, vis., TCPSocket#send and TCPSocket#read.
22
+ class SSLSocket
23
+ include SocketUtil
24
+
25
+ def initialize(host, port, op_timeout=nil, connect_timeout=nil, opts={})
26
+ @op_timeout = op_timeout
27
+ @connect_timeout = connect_timeout
28
+ @pid = Process.pid
29
+ @auths = Set.new
30
+
31
+ @tcp_socket = ::TCPSocket.new(host, port)
32
+ @tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
33
+
34
+ @context = OpenSSL::SSL::SSLContext.new
35
+
36
+ if opts[:cert]
37
+ @context.cert = OpenSSL::X509::Certificate.new(File.open(opts[:cert]))
38
+ end
39
+
40
+ if opts[:key]
41
+ if opts[:key_pass_phrase]
42
+ @context.key = OpenSSL::PKey::RSA.new(File.open(opts[:key]), opts[:key_pass_phrase])
43
+ else
44
+ @context.key = OpenSSL::PKey::RSA.new(File.open(opts[:key]))
45
+ end
46
+ end
47
+
48
+ if opts[:verify]
49
+ @context.ca_file = opts[:ca_cert]
50
+ @context.verify_mode = OpenSSL::SSL::VERIFY_PEER
51
+ end
52
+
53
+ begin
54
+ @socket = OpenSSL::SSL::SSLSocket.new(@tcp_socket, @context)
55
+ @socket.sync_close = true
56
+ connect
57
+ rescue OpenSSL::SSL::SSLError
58
+ raise ConnectionFailure, "SSL handshake failed. MongoDB may " +
59
+ "not be configured with SSL support."
60
+ end
61
+
62
+ if opts[:verify]
63
+ unless OpenSSL::SSL.verify_certificate_identity(@socket.peer_cert, host)
64
+ raise ConnectionFailure, "SSL handshake failed. Hostname mismatch."
65
+ end
66
+ end
67
+
68
+ self
69
+ end
70
+
71
+ def connect
72
+ if @connect_timeout
73
+ Timeout::timeout(@connect_timeout, ConnectionTimeoutError) do
74
+ @socket.connect
75
+ end
76
+ else
77
+ @socket.connect
78
+ end
79
+ end
80
+
81
+ def send(data)
82
+ @socket.syswrite(data)
83
+ end
84
+
85
+ def read(length, buffer)
86
+ if @op_timeout
87
+ Timeout::timeout(@op_timeout, OperationTimeout) do
88
+ @socket.sysread(length, buffer)
89
+ end
90
+ else
91
+ @socket.sysread(length, buffer)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,86 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ # Wrapper class for Socket
17
+ #
18
+ # Emulates TCPSocket with operation and connection timeout
19
+ # sans Timeout::timeout
20
+ #
21
+ class TCPSocket
22
+ include SocketUtil
23
+
24
+ def initialize(host, port, op_timeout=nil, connect_timeout=nil, opts={})
25
+ @op_timeout = op_timeout
26
+ @connect_timeout = connect_timeout
27
+ @pid = Process.pid
28
+ @auths = Set.new
29
+
30
+ @socket = handle_connect(host, port)
31
+ end
32
+
33
+ def handle_connect(host, port)
34
+ error = nil
35
+ # Following python's lead (see PYTHON-356)
36
+ family = host == 'localhost' ? Socket::AF_INET : Socket::AF_UNSPEC
37
+ addr_info = Socket.getaddrinfo(host, nil, family, Socket::SOCK_STREAM)
38
+ addr_info.each do |info|
39
+ begin
40
+ sock = Socket.new(info[4], Socket::SOCK_STREAM, 0)
41
+ sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
42
+ socket_address = Socket.pack_sockaddr_in(port, info[3])
43
+ connect(sock, socket_address)
44
+ return sock
45
+ rescue IOError, SystemCallError => e
46
+ error = e
47
+ sock.close if sock
48
+ end
49
+ end
50
+ raise error
51
+ end
52
+
53
+ def connect(socket, socket_address)
54
+ if @connect_timeout
55
+ Timeout::timeout(@connect_timeout, ConnectionTimeoutError) do
56
+ socket.connect(socket_address)
57
+ end
58
+ else
59
+ socket.connect(socket_address)
60
+ end
61
+ end
62
+
63
+ def send(data)
64
+ @socket.write(data)
65
+ end
66
+
67
+ def read(maxlen, buffer)
68
+ # Block on data to read for @op_timeout seconds
69
+ begin
70
+ ready = IO.select([@socket], nil, [@socket], @op_timeout)
71
+ unless ready
72
+ raise OperationTimeout
73
+ end
74
+ rescue IOError
75
+ raise ConnectionFailure
76
+ end
77
+
78
+ # Read data from socket
79
+ begin
80
+ @socket.sysread(maxlen, buffer)
81
+ rescue SystemCallError, IOError => ex
82
+ raise ConnectionFailure, ex
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,39 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+
17
+ attr_accessor :auths
18
+
19
+ # Wrapper class for Socket
20
+ #
21
+ # Emulates UNIXSocket with operation and connection timeout
22
+ # sans Timeout::timeout
23
+ #
24
+ class UNIXSocket < TCPSocket
25
+ def initialize(socket_path, port=:socket, op_timeout=nil, connect_timeout=nil, opts={})
26
+ @op_timeout = op_timeout
27
+ @connect_timeout = connect_timeout
28
+ @pid = Process.pid
29
+ @auths = Set.new
30
+
31
+ @address = socket_path
32
+ @port = :socket # purposely override input
33
+
34
+ @socket_address = Socket.pack_sockaddr_un(@address)
35
+ @socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
36
+ connect
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,719 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+
17
+ # A cursor over query results. Returned objects are hashes.
18
+ class Cursor
19
+ include Enumerable
20
+ include Mongo::Constants
21
+ include Mongo::Conversions
22
+ include Mongo::Logging
23
+ include Mongo::ReadPreference
24
+
25
+ attr_reader :collection, :selector, :fields,
26
+ :order, :hint, :snapshot, :timeout,
27
+ :full_collection_name, :transformer,
28
+ :options, :cursor_id, :show_disk_loc,
29
+ :comment, :compile_regex, :read, :tag_sets,
30
+ :acceptable_latency
31
+
32
+ # Create a new cursor.
33
+ #
34
+ # Note: cursors are created when executing queries using [Collection#find] and other
35
+ # similar methods. Application developers shouldn't have to create cursors manually.
36
+ #
37
+ # @return [Cursor]
38
+ def initialize(collection, opts={})
39
+ opts = opts.dup
40
+ @cursor_id = opts.delete(:cursor_id)
41
+ @db = collection.db
42
+ @collection = collection
43
+ @connection = @db.connection
44
+ @logger = @connection.logger
45
+
46
+ # Query selector
47
+ @selector = opts.delete(:selector) || {}
48
+
49
+ # Query pre-serialized bson to append
50
+ @bson = @selector.delete(:bson)
51
+
52
+ # Special operators that form part of $query
53
+ @order = opts.delete(:order)
54
+ @explain = opts.delete(:explain)
55
+ @hint = opts.delete(:hint)
56
+ @snapshot = opts.delete(:snapshot)
57
+ @max_scan = opts.delete(:max_scan)
58
+ @return_key = opts.delete(:return_key)
59
+ @show_disk_loc = opts.delete(:show_disk_loc)
60
+ @comment = opts.delete(:comment)
61
+ @compile_regex = opts.key?(:compile_regex) ? opts.delete(:compile_regex) : true
62
+
63
+ # Wire-protocol settings
64
+ @fields = convert_fields_for_query(opts.delete(:fields))
65
+ @skip = opts.delete(:skip) || 0
66
+ @limit = opts.delete(:limit) || 0
67
+ @tailable = opts.delete(:tailable)
68
+ @timeout = opts.key?(:timeout) ? opts.delete(:timeout) : true
69
+ @options = 0
70
+
71
+ # Use this socket for the query
72
+ @socket = opts.delete(:socket)
73
+ @pool = opts.delete(:pool)
74
+
75
+ @closed = false
76
+ @query_run = false
77
+
78
+ @transformer = opts.delete(:transformer)
79
+ @read = opts.delete(:read) || @collection.read
80
+ Mongo::ReadPreference::validate(@read)
81
+ @tag_sets = opts.delete(:tag_sets) || @collection.tag_sets
82
+ @acceptable_latency = opts.delete(:acceptable_latency) || @collection.acceptable_latency
83
+
84
+ batch_size(opts.delete(:batch_size) || 0)
85
+
86
+ @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
87
+ @cache = opts.delete(:first_batch) || []
88
+ @returned = 0
89
+
90
+ if(!@timeout)
91
+ add_option(OP_QUERY_NO_CURSOR_TIMEOUT)
92
+ end
93
+ if(@read != :primary)
94
+ add_option(OP_QUERY_SLAVE_OK)
95
+ end
96
+ if(@tailable)
97
+ add_option(OP_QUERY_TAILABLE)
98
+ end
99
+
100
+ # If a cursor_id is provided, this is a cursor for a command
101
+ if @cursor_id
102
+ @command_cursor = true
103
+ @query_run = true
104
+ end
105
+
106
+ if @collection.name =~ /^\$cmd/ || @collection.name =~ /^system/
107
+ @command = true
108
+ else
109
+ @command = false
110
+ end
111
+
112
+ @opts = opts
113
+ end
114
+
115
+ # Guess whether the cursor is alive on the server.
116
+ #
117
+ # Note that this method only checks whether we have
118
+ # a cursor id. The cursor may still have timed out
119
+ # on the server. This will be indicated in the next
120
+ # call to Cursor#next.
121
+ #
122
+ # @return [Boolean]
123
+ def alive?
124
+ @cursor_id && @cursor_id != 0
125
+ end
126
+
127
+ # Get the next document specified the cursor options.
128
+ #
129
+ # @return [Hash, Nil] the next document or Nil if no documents remain.
130
+ def next
131
+ if @cache.length == 0
132
+ if @query_run && exhaust?
133
+ close
134
+ return nil
135
+ else
136
+ refresh
137
+ end
138
+ end
139
+ doc = @cache.shift
140
+
141
+ if doc && (err = doc['errmsg'] || doc['$err']) # assignment
142
+ code = doc['code']
143
+
144
+ # If the server has stopped being the master (e.g., it's one of a
145
+ # pair but it has died or something like that) then we close that
146
+ # connection. The next request will re-open on master server.
147
+ if err.include?("not master")
148
+ @connection.close
149
+ raise ConnectionFailure.new(err, code, doc)
150
+ end
151
+
152
+ # Handle server side operation execution timeout
153
+ if code == 50
154
+ raise ExecutionTimeout.new(err, code, doc)
155
+ end
156
+
157
+ raise OperationFailure.new(err, code, doc)
158
+ elsif doc && (write_concern_error = doc['writeConcernError']) # assignment
159
+ raise WriteConcernError.new(write_concern_error['errmsg'], write_concern_error['code'], doc)
160
+ end
161
+
162
+ if @transformer.nil?
163
+ doc
164
+ else
165
+ @transformer.call(doc) if doc
166
+ end
167
+ end
168
+ alias :next_document :next
169
+
170
+ # Reset this cursor on the server. Cursor options, such as the
171
+ # query string and the values for skip and limit, are preserved.
172
+ def rewind!
173
+ check_command_cursor
174
+ close
175
+ @cache.clear
176
+ @cursor_id = nil
177
+ @closed = false
178
+ @query_run = false
179
+ @n_received = nil
180
+ true
181
+ end
182
+
183
+ # Determine whether this cursor has any remaining results.
184
+ #
185
+ # @return [Boolean]
186
+ def has_next?
187
+ num_remaining > 0
188
+ end
189
+
190
+ # Get the size of the result set for this query.
191
+ #
192
+ # @param [Boolean] skip_and_limit whether or not to take skip or limit into account.
193
+ #
194
+ # @return [Integer] the number of objects in the result set for this query.
195
+ #
196
+ # @raise [OperationFailure] on a database error.
197
+ def count(skip_and_limit = false)
198
+ check_command_cursor
199
+ command = BSON::OrderedHash["count", @collection.name, "query", @selector]
200
+
201
+ if skip_and_limit
202
+ command.merge!(BSON::OrderedHash["limit", @limit]) if @limit != 0
203
+ command.merge!(BSON::OrderedHash["skip", @skip]) if @skip != 0
204
+ end
205
+
206
+ command.merge!(BSON::OrderedHash["fields", @fields])
207
+
208
+ response = @db.command(command, :read => @read, :comment => @comment)
209
+ return response['n'].to_i if Mongo::Support.ok?(response)
210
+ return 0 if response['errmsg'] == "ns missing"
211
+ raise OperationFailure.new("Count failed: #{response['errmsg']}", response['code'], response)
212
+ end
213
+
214
+ # Sort this cursor's results.
215
+ #
216
+ # This method overrides any sort order specified in the Collection#find
217
+ # method, and only the last sort applied has an effect.
218
+ #
219
+ # @param [Symbol, Array, Hash, OrderedHash] order either 1) a key to sort by 2)
220
+ # an array of [key, direction] pairs to sort by or 3) a hash of
221
+ # field => direction pairs to sort by. Direction should be specified as
222
+ # Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING
223
+ # (or :descending / :desc)
224
+ #
225
+ # @raise [InvalidOperation] if this cursor has already been used.
226
+ #
227
+ # @raise [InvalidSortValueError] if the specified order is invalid.
228
+ def sort(order, direction=nil)
229
+ check_modifiable
230
+ order = [[order, direction]] unless direction.nil?
231
+ @order = order
232
+ self
233
+ end
234
+
235
+ # Limit the number of results to be returned by this cursor.
236
+ #
237
+ # This method overrides any limit specified in the Collection#find method,
238
+ # and only the last limit applied has an effect.
239
+ #
240
+ # @return [Integer] the current number_to_return if no parameter is given.
241
+ #
242
+ # @raise [InvalidOperation] if this cursor has already been used.
243
+ def limit(number_to_return=nil)
244
+ return @limit unless number_to_return
245
+ check_modifiable
246
+
247
+ if (number_to_return != 0) && exhaust?
248
+ raise MongoArgumentError, "Limit is incompatible with exhaust option."
249
+ end
250
+
251
+ @limit = number_to_return
252
+ self
253
+ end
254
+
255
+ # Skips the first +number_to_skip+ results of this cursor.
256
+ # Returns the current number_to_skip if no parameter is given.
257
+ #
258
+ # This method overrides any skip specified in the Collection#find method,
259
+ # and only the last skip applied has an effect.
260
+ #
261
+ # @return [Integer]
262
+ #
263
+ # @raise [InvalidOperation] if this cursor has already been used.
264
+ def skip(number_to_skip=nil)
265
+ return @skip unless number_to_skip
266
+ check_modifiable
267
+
268
+ @skip = number_to_skip
269
+ self
270
+ end
271
+
272
+ # Instruct the server to abort queries after they exceed the specified
273
+ # wall-clock execution time.
274
+ #
275
+ # A query that completes in under its time limit will "roll over"
276
+ # remaining time to the first getmore op (which will then "roll over"
277
+ # its remaining time to the second getmore op and so on, until the
278
+ # time limit is hit).
279
+ #
280
+ # Cursors returned by successful time-limited queries will still obey
281
+ # the default cursor idle timeout (unless the "no cursor idle timeout"
282
+ # flag has been set).
283
+ #
284
+ # @note This will only have an effect in MongoDB 2.5+
285
+ #
286
+ # @param max_time_ms [Fixnum] max execution time (in milliseconds)
287
+ #
288
+ # @return [Fixnum, Cursor] either the current max_time_ms or cursor
289
+ def max_time_ms(max_time_ms=nil)
290
+ return @max_time_ms unless max_time_ms
291
+ check_modifiable
292
+
293
+ @max_time_ms = max_time_ms
294
+ self
295
+ end
296
+
297
+ # Set the batch size for server responses.
298
+ #
299
+ # Note that the batch size will take effect only on queries
300
+ # where the number to be returned is greater than 100.
301
+ #
302
+ # This can not override MongoDB's limit on the amount of data it will
303
+ # return to the client. Depending on server version this can be 4-16mb.
304
+ #
305
+ # @param [Integer] size either 0 or some integer greater than 1. If 0,
306
+ # the server will determine the batch size.
307
+ #
308
+ # @return [Cursor]
309
+ def batch_size(size=nil)
310
+ return @batch_size unless size
311
+ check_modifiable
312
+ if size < 0 || size == 1
313
+ raise ArgumentError, "Invalid value for batch_size #{size}; must be 0 or > 1."
314
+ else
315
+ @batch_size = @limit != 0 && size > @limit ? @limit : size
316
+ end
317
+
318
+ self
319
+ end
320
+
321
+ # Iterate over each document in this cursor, yielding it to the given
322
+ # block, if provided. An Enumerator is returned if no block is given.
323
+ #
324
+ # Iterating over an entire cursor will close it.
325
+ #
326
+ # @yield passes each document to a block for processing.
327
+ #
328
+ # @example if 'comments' represents a collection of comments:
329
+ # comments.find.each do |doc|
330
+ # puts doc['user']
331
+ # end
332
+ def each
333
+ if block_given? || !defined?(Enumerator)
334
+ while doc = self.next
335
+ yield doc
336
+ end
337
+ else
338
+ Enumerator.new do |yielder|
339
+ while doc = self.next
340
+ yielder.yield doc
341
+ end
342
+ end
343
+ end
344
+ end
345
+ # Receive all the documents from this cursor as an array of hashes.
346
+ #
347
+ # Notes:
348
+ #
349
+ # If you've already started iterating over the cursor, the array returned
350
+ # by this method contains only the remaining documents. See Cursor#rewind! if you
351
+ # need to reset the cursor.
352
+ #
353
+ # Use of this method is discouraged - in most cases, it's much more
354
+ # efficient to retrieve documents as you need them by iterating over the cursor.
355
+ #
356
+ # @return [Array] an array of documents.
357
+ def to_a
358
+ super
359
+ end
360
+
361
+ # Get the explain plan for this cursor.
362
+ #
363
+ # @return [Hash] a document containing the explain plan for this cursor.
364
+ def explain
365
+ check_command_cursor
366
+ c = Cursor.new(@collection,
367
+ query_options_hash.merge(:limit => -@limit.abs, :explain => true))
368
+ explanation = c.next_document
369
+ c.close
370
+
371
+ explanation
372
+ end
373
+
374
+ # Close the cursor.
375
+ #
376
+ # Note: if a cursor is read until exhausted (read until Mongo::Constants::OP_QUERY or
377
+ # Mongo::Constants::OP_GETMORE returns zero for the cursor id), there is no need to
378
+ # close it manually.
379
+ #
380
+ # Note also: Collection#find takes an optional block argument which can be used to
381
+ # ensure that your cursors get closed.
382
+ #
383
+ # @return [True]
384
+ def close
385
+ if @cursor_id && @cursor_id != 0
386
+ message = BSON::ByteBuffer.new([0, 0, 0, 0])
387
+ message.put_int(1)
388
+ message.put_long(@cursor_id)
389
+ log(:debug, "Cursor#close #{@cursor_id}")
390
+ @connection.send_message(
391
+ Mongo::Constants::OP_KILL_CURSORS,
392
+ message,
393
+ :pool => @pool
394
+ )
395
+ end
396
+ @cursor_id = 0
397
+ @closed = true
398
+ end
399
+
400
+ # Is this cursor closed?
401
+ #
402
+ # @return [Boolean]
403
+ def closed?
404
+ @closed
405
+ end
406
+
407
+ # Returns an integer indicating which query options have been selected.
408
+ #
409
+ # @return [Integer]
410
+ #
411
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
412
+ # The MongoDB wire protocol.
413
+ def query_opts
414
+ warn "The method Cursor#query_opts has been deprecated " +
415
+ "and will removed in v2.0. Use Cursor#options instead."
416
+ @options
417
+ end
418
+
419
+ # Add an option to the query options bitfield.
420
+ #
421
+ # @param opt a valid query option
422
+ #
423
+ # @raise InvalidOperation if this method is run after the cursor has bee
424
+ # iterated for the first time.
425
+ #
426
+ # @return [Integer] the current value of the options bitfield for this cursor.
427
+ #
428
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
429
+ def add_option(opt)
430
+ check_modifiable
431
+
432
+ if exhaust?(opt)
433
+ if @limit != 0
434
+ raise MongoArgumentError, "Exhaust is incompatible with limit."
435
+ elsif @connection.mongos?
436
+ raise MongoArgumentError, "Exhaust is incompatible with mongos."
437
+ end
438
+ end
439
+
440
+ @options |= opt
441
+ @options
442
+ end
443
+
444
+ # Remove an option from the query options bitfield.
445
+ #
446
+ # @param opt a valid query option
447
+ #
448
+ # @raise InvalidOperation if this method is run after the cursor has bee
449
+ # iterated for the first time.
450
+ #
451
+ # @return [Integer] the current value of the options bitfield for this cursor.
452
+ #
453
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
454
+ def remove_option(opt)
455
+ check_modifiable
456
+
457
+ @options &= ~opt
458
+ @options
459
+ end
460
+
461
+ # Get the query options for this Cursor.
462
+ #
463
+ # @return [Hash]
464
+ def query_options_hash
465
+ BSON::OrderedHash[
466
+ :selector => @selector,
467
+ :fields => @fields,
468
+ :skip => @skip,
469
+ :limit => @limit,
470
+ :order => @order,
471
+ :hint => @hint,
472
+ :snapshot => @snapshot,
473
+ :timeout => @timeout,
474
+ :max_scan => @max_scan,
475
+ :return_key => @return_key,
476
+ :show_disk_loc => @show_disk_loc,
477
+ :comment => @comment ]
478
+ end
479
+
480
+ # Clean output for inspect.
481
+ def inspect
482
+ "<Mongo::Cursor:0x#{object_id.to_s(16)} namespace='#{@db.name}.#{@collection.name}' " +
483
+ "@selector=#{@selector.inspect} @cursor_id=#{@cursor_id}>"
484
+ end
485
+
486
+ private
487
+
488
+ # Convert the +:fields+ parameter from a single field name or an array
489
+ # of fields names to a hash, with the field names for keys and '1' for each
490
+ # value.
491
+ def convert_fields_for_query(fields)
492
+ case fields
493
+ when String, Symbol
494
+ {fields => 1}
495
+ when Array
496
+ return nil if fields.length.zero?
497
+ fields.inject({}) do |hash, field|
498
+ field.is_a?(Hash) ? hash.merge!(field) : hash[field] = 1
499
+ hash
500
+ end
501
+ when Hash
502
+ return fields
503
+ end
504
+ end
505
+
506
+ # Return the number of documents remaining for this cursor.
507
+ def num_remaining
508
+ if @cache.length == 0
509
+ if @query_run && exhaust?
510
+ close
511
+ return 0
512
+ else
513
+ refresh
514
+ end
515
+ end
516
+
517
+ @cache.length
518
+ end
519
+
520
+ # Refresh the documents in @cache. This means either
521
+ # sending the initial query or sending a GET_MORE operation.
522
+ def refresh
523
+ if !@query_run
524
+ send_initial_query
525
+ elsif !@cursor_id.zero?
526
+ send_get_more
527
+ end
528
+ end
529
+
530
+ # Sends initial query -- which is always a read unless it is a command
531
+ #
532
+ # Upon ConnectionFailure, tries query 3 times if socket was not provided
533
+ # and the query is either not a command or is a secondary_ok command.
534
+ #
535
+ # Pins pools upon successful read and unpins pool upon ConnectionFailure
536
+ #
537
+ def send_initial_query
538
+ tries = 0
539
+ instrument(:find, instrument_payload) do
540
+ begin
541
+ message = construct_query_message
542
+ socket = @socket || checkout_socket_from_connection
543
+ results, @n_received, @cursor_id = @connection.receive_message(
544
+ Mongo::Constants::OP_QUERY, message, nil, socket, @command,
545
+ nil, exhaust?, compile_regex?)
546
+ rescue ConnectionFailure => ex
547
+ socket.close if socket
548
+ @pool = nil
549
+ @connection.unpin_pool
550
+ @connection.refresh
551
+ if tries < 3 && !@socket && (!@command || Mongo::ReadPreference::secondary_ok?(@selector))
552
+ tries += 1
553
+ retry
554
+ else
555
+ raise ex
556
+ end
557
+ rescue OperationFailure, OperationTimeout => ex
558
+ raise ex
559
+ ensure
560
+ socket.checkin unless @socket || socket.nil?
561
+ end
562
+ if !@socket && !@command
563
+ @connection.pin_pool(socket.pool, read_preference)
564
+ end
565
+ @returned += @n_received
566
+ @cache += results
567
+ @query_run = true
568
+ close_cursor_if_query_complete
569
+ end
570
+ end
571
+
572
+ def send_get_more
573
+ message = BSON::ByteBuffer.new([0, 0, 0, 0])
574
+
575
+ # DB name.
576
+ BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
577
+
578
+ # Number of results to return.
579
+ if @limit > 0
580
+ limit = @limit - @returned
581
+ if @batch_size > 0
582
+ limit = limit < @batch_size ? limit : @batch_size
583
+ end
584
+ message.put_int(limit)
585
+ else
586
+ message.put_int(@batch_size)
587
+ end
588
+
589
+ # Cursor id.
590
+ message.put_long(@cursor_id)
591
+ log(:debug, "cursor.refresh() for cursor #{@cursor_id}") if @logger
592
+
593
+ socket = @pool.checkout
594
+
595
+ begin
596
+ results, @n_received, @cursor_id = @connection.receive_message(
597
+ Mongo::Constants::OP_GET_MORE, message, nil, socket, @command,
598
+ nil, exhaust?, compile_regex?)
599
+ ensure
600
+ socket.checkin
601
+ end
602
+
603
+ @returned += @n_received
604
+ @cache += results
605
+ close_cursor_if_query_complete
606
+ end
607
+
608
+ def checkout_socket_from_connection
609
+ begin
610
+ if @pool
611
+ socket = @pool.checkout
612
+ elsif @command && !Mongo::ReadPreference::secondary_ok?(@selector)
613
+ socket = @connection.checkout_reader({:mode => :primary})
614
+ else
615
+ socket = @connection.checkout_reader(read_preference)
616
+ end
617
+ rescue SystemStackError, NoMemoryError, SystemCallError => ex
618
+ @connection.close
619
+ raise ex
620
+ end
621
+ @pool = socket.pool
622
+ socket
623
+ end
624
+
625
+ def checkin_socket(sock)
626
+ @connection.checkin(sock)
627
+ end
628
+
629
+ def construct_query_message
630
+ message = BSON::ByteBuffer.new("", @connection.max_bson_size + MongoClient::COMMAND_HEADROOM)
631
+ message.put_int(@options)
632
+ BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
633
+ message.put_int(@skip)
634
+ @batch_size > 1 ? message.put_int(@batch_size) : message.put_int(@limit)
635
+ if query_contains_special_fields? && @bson # costs two serialize calls
636
+ query_message = BSON::BSON_CODER.serialize(@selector, false, false, @connection.max_bson_size + MongoClient::APPEND_HEADROOM)
637
+ query_message.grow(@bson)
638
+ query_spec = construct_query_spec
639
+ query_spec.delete('$query')
640
+ query_message.grow(BSON::BSON_CODER.serialize(query_spec, false, false, @connection.max_bson_size))
641
+ else # costs only one serialize call
642
+ spec = query_contains_special_fields? ? construct_query_spec : @selector
643
+ spec.merge!(@opts)
644
+ query_message = BSON::BSON_CODER.serialize(spec, false, false, @connection.max_bson_size + MongoClient::APPEND_HEADROOM)
645
+ query_message.grow(@bson) if @bson
646
+ end
647
+ message.put_binary(query_message.to_s)
648
+ message.put_binary(BSON::BSON_CODER.serialize(@fields, false, false, @connection.max_bson_size).to_s) if @fields
649
+ message
650
+ end
651
+
652
+ def instrument_payload
653
+ log = { :database => @db.name, :collection => @collection.name, :selector => selector }
654
+ log[:fields] = @fields if @fields
655
+ log[:skip] = @skip if @skip && (@skip != 0)
656
+ log[:limit] = @limit if @limit && (@limit != 0)
657
+ log[:order] = @order if @order
658
+ log
659
+ end
660
+
661
+ def construct_query_spec
662
+ return @selector if @selector.has_key?('$query')
663
+ spec = BSON::OrderedHash.new
664
+ spec['$query'] = @selector
665
+ spec['$orderby'] = Mongo::Support.format_order_clause(@order) if @order
666
+ spec['$hint'] = @hint if @hint && @hint.length > 0
667
+ spec['$explain'] = true if @explain
668
+ spec['$snapshot'] = true if @snapshot
669
+ spec['$maxScan'] = @max_scan if @max_scan
670
+ spec['$returnKey'] = true if @return_key
671
+ spec['$showDiskLoc'] = true if @show_disk_loc
672
+ spec['$comment'] = @comment if @comment
673
+ spec['$maxTimeMS'] = @max_time_ms if @max_time_ms
674
+ if needs_read_pref?
675
+ read_pref = Mongo::ReadPreference::mongos(@read, @tag_sets)
676
+ spec['$readPreference'] = read_pref if read_pref
677
+ end
678
+ spec
679
+ end
680
+
681
+ def needs_read_pref?
682
+ @connection.mongos? && @read != :primary
683
+ end
684
+
685
+ def query_contains_special_fields?
686
+ @order || @explain || @hint || @snapshot || @show_disk_loc ||
687
+ @max_scan || @return_key || @comment || @max_time_ms || needs_read_pref?
688
+ end
689
+
690
+ def close_cursor_if_query_complete
691
+ if @limit > 0 && @returned >= @limit
692
+ close
693
+ end
694
+ end
695
+
696
+ # Check whether the exhaust option is set
697
+ #
698
+ # @return [true, false] The state of the exhaust flag.
699
+ def exhaust?(opts = options)
700
+ !(opts & OP_QUERY_EXHAUST).zero?
701
+ end
702
+
703
+ def check_modifiable
704
+ if @query_run || @closed
705
+ raise InvalidOperation, "Cannot modify the query once it has been run or closed."
706
+ end
707
+ end
708
+
709
+ def check_command_cursor
710
+ if @command_cursor
711
+ raise InvalidOperation, "Cannot call #{caller.first} on command cursors"
712
+ end
713
+ end
714
+
715
+ def compile_regex?
716
+ @compile_regex
717
+ end
718
+ end
719
+ end