mongo 1.10.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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