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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/LICENSE +190 -0
- data/README.md +149 -0
- data/Rakefile +31 -0
- data/VERSION +1 -0
- data/bin/mongo_console +43 -0
- data/ext/jsasl/target/jsasl.jar +0 -0
- data/lib/mongo.rb +90 -0
- data/lib/mongo/bulk_write_collection_view.rb +380 -0
- data/lib/mongo/collection.rb +1164 -0
- data/lib/mongo/collection_writer.rb +364 -0
- data/lib/mongo/connection.rb +19 -0
- data/lib/mongo/connection/node.rb +239 -0
- data/lib/mongo/connection/pool.rb +347 -0
- data/lib/mongo/connection/pool_manager.rb +325 -0
- data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
- data/lib/mongo/connection/socket.rb +18 -0
- data/lib/mongo/connection/socket/socket_util.rb +37 -0
- data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
- data/lib/mongo/connection/socket/tcp_socket.rb +86 -0
- data/lib/mongo/connection/socket/unix_socket.rb +39 -0
- data/lib/mongo/cursor.rb +719 -0
- data/lib/mongo/db.rb +735 -0
- data/lib/mongo/exception.rb +88 -0
- data/lib/mongo/functional.rb +21 -0
- data/lib/mongo/functional/authentication.rb +318 -0
- data/lib/mongo/functional/logging.rb +85 -0
- data/lib/mongo/functional/read_preference.rb +174 -0
- data/lib/mongo/functional/sasl_java.rb +48 -0
- data/lib/mongo/functional/uri_parser.rb +374 -0
- data/lib/mongo/functional/write_concern.rb +66 -0
- data/lib/mongo/gridfs.rb +18 -0
- data/lib/mongo/gridfs/grid.rb +112 -0
- data/lib/mongo/gridfs/grid_ext.rb +53 -0
- data/lib/mongo/gridfs/grid_file_system.rb +163 -0
- data/lib/mongo/gridfs/grid_io.rb +484 -0
- data/lib/mongo/legacy.rb +140 -0
- data/lib/mongo/mongo_client.rb +702 -0
- data/lib/mongo/mongo_replica_set_client.rb +523 -0
- data/lib/mongo/mongo_sharded_client.rb +159 -0
- data/lib/mongo/networking.rb +370 -0
- data/lib/mongo/utils.rb +19 -0
- data/lib/mongo/utils/conversions.rb +110 -0
- data/lib/mongo/utils/core_ext.rb +70 -0
- data/lib/mongo/utils/server_version.rb +69 -0
- data/lib/mongo/utils/support.rb +80 -0
- data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
- data/mongo.gemspec +36 -0
- data/test/functional/authentication_test.rb +35 -0
- data/test/functional/bulk_api_stress_test.rb +133 -0
- data/test/functional/bulk_write_collection_view_test.rb +1129 -0
- data/test/functional/client_test.rb +565 -0
- data/test/functional/collection_test.rb +2073 -0
- data/test/functional/collection_writer_test.rb +83 -0
- data/test/functional/conversions_test.rb +163 -0
- data/test/functional/cursor_fail_test.rb +63 -0
- data/test/functional/cursor_message_test.rb +57 -0
- data/test/functional/cursor_test.rb +625 -0
- data/test/functional/db_api_test.rb +819 -0
- data/test/functional/db_connection_test.rb +27 -0
- data/test/functional/db_test.rb +344 -0
- data/test/functional/grid_file_system_test.rb +285 -0
- data/test/functional/grid_io_test.rb +252 -0
- data/test/functional/grid_test.rb +273 -0
- data/test/functional/pool_test.rb +62 -0
- data/test/functional/safe_test.rb +98 -0
- data/test/functional/ssl_test.rb +29 -0
- data/test/functional/support_test.rb +62 -0
- data/test/functional/timeout_test.rb +58 -0
- data/test/functional/uri_test.rb +330 -0
- data/test/functional/write_concern_test.rb +118 -0
- data/test/helpers/general.rb +50 -0
- data/test/helpers/test_unit.rb +317 -0
- data/test/replica_set/authentication_test.rb +35 -0
- data/test/replica_set/basic_test.rb +174 -0
- data/test/replica_set/client_test.rb +341 -0
- data/test/replica_set/complex_connect_test.rb +77 -0
- data/test/replica_set/connection_test.rb +138 -0
- data/test/replica_set/count_test.rb +64 -0
- data/test/replica_set/cursor_test.rb +212 -0
- data/test/replica_set/insert_test.rb +140 -0
- data/test/replica_set/max_values_test.rb +145 -0
- data/test/replica_set/pinning_test.rb +55 -0
- data/test/replica_set/query_test.rb +73 -0
- data/test/replica_set/read_preference_test.rb +214 -0
- data/test/replica_set/refresh_test.rb +175 -0
- data/test/replica_set/replication_ack_test.rb +94 -0
- data/test/replica_set/ssl_test.rb +32 -0
- data/test/sharded_cluster/basic_test.rb +197 -0
- data/test/shared/authentication/basic_auth_shared.rb +286 -0
- data/test/shared/authentication/bulk_api_auth_shared.rb +259 -0
- data/test/shared/authentication/gssapi_shared.rb +164 -0
- data/test/shared/authentication/sasl_plain_shared.rb +96 -0
- data/test/shared/ssl_shared.rb +235 -0
- data/test/test_helper.rb +56 -0
- data/test/threading/basic_test.rb +120 -0
- data/test/tools/mongo_config.rb +608 -0
- data/test/tools/mongo_config_test.rb +160 -0
- data/test/unit/client_test.rb +347 -0
- data/test/unit/collection_test.rb +166 -0
- data/test/unit/connection_test.rb +325 -0
- data/test/unit/cursor_test.rb +299 -0
- data/test/unit/db_test.rb +136 -0
- data/test/unit/grid_test.rb +76 -0
- data/test/unit/mongo_sharded_client_test.rb +48 -0
- data/test/unit/node_test.rb +93 -0
- data/test/unit/pool_manager_test.rb +142 -0
- data/test/unit/read_pref_test.rb +115 -0
- data/test/unit/read_test.rb +159 -0
- data/test/unit/safe_test.rb +158 -0
- data/test/unit/sharding_pool_manager_test.rb +84 -0
- data/test/unit/write_concern_test.rb +175 -0
- metadata +260 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
# Instantiates and manages connections to a MongoDB sharded cluster for high availability.
|
|
18
|
+
class MongoShardedClient < MongoReplicaSetClient
|
|
19
|
+
include ThreadLocalVariableManager
|
|
20
|
+
|
|
21
|
+
SHARDED_CLUSTER_OPTS = [:refresh_mode, :refresh_interval, :tag_sets, :read]
|
|
22
|
+
|
|
23
|
+
attr_reader :seeds, :refresh_interval, :refresh_mode,
|
|
24
|
+
:refresh_version, :manager
|
|
25
|
+
|
|
26
|
+
def initialize(*args)
|
|
27
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
|
28
|
+
|
|
29
|
+
nodes = args.flatten
|
|
30
|
+
|
|
31
|
+
if nodes.empty? and ENV.has_key?('MONGODB_URI')
|
|
32
|
+
parser = URIParser.new ENV['MONGODB_URI']
|
|
33
|
+
opts = parser.connection_options.merge! opts
|
|
34
|
+
nodes = parser.node_strings
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
unless nodes.length > 0
|
|
38
|
+
raise MongoArgumentError, "A MongoShardedClient requires at least one seed node."
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@seeds = nodes.map do |host_port|
|
|
42
|
+
Support.normalize_seeds(host_port)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# TODO: add a method for replacing this list of node.
|
|
46
|
+
@seeds.freeze
|
|
47
|
+
|
|
48
|
+
# Refresh
|
|
49
|
+
@last_refresh = Time.now
|
|
50
|
+
@refresh_version = 0
|
|
51
|
+
|
|
52
|
+
# No connection manager by default.
|
|
53
|
+
@manager = nil
|
|
54
|
+
|
|
55
|
+
# Lock for request ids.
|
|
56
|
+
@id_lock = Mutex.new
|
|
57
|
+
|
|
58
|
+
@connected = false
|
|
59
|
+
|
|
60
|
+
@connect_mutex = Mutex.new
|
|
61
|
+
|
|
62
|
+
@mongos = true
|
|
63
|
+
|
|
64
|
+
check_opts(opts)
|
|
65
|
+
setup(opts)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def valid_opts
|
|
69
|
+
super + SHARDED_CLUSTER_OPTS
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def inspect
|
|
73
|
+
"<Mongo::MongoShardedClient:0x#{self.object_id.to_s(16)} @seeds=#{@seeds.inspect} " +
|
|
74
|
+
"@connected=#{@connected}>"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Initiate a connection to the sharded cluster.
|
|
78
|
+
def connect(force = !connected?)
|
|
79
|
+
return unless force
|
|
80
|
+
log(:info, "Connecting...")
|
|
81
|
+
|
|
82
|
+
# Prevent recursive connection attempts from the same thread.
|
|
83
|
+
# This is done rather than using a Monitor to prevent potentially recursing
|
|
84
|
+
# infinitely while attempting to connect and continually failing. Instead, fail fast.
|
|
85
|
+
raise ConnectionFailure, "Failed to get node data." if thread_local[:locks][:connecting]
|
|
86
|
+
|
|
87
|
+
@connect_mutex.synchronize do
|
|
88
|
+
begin
|
|
89
|
+
thread_local[:locks][:connecting] = true
|
|
90
|
+
if @manager
|
|
91
|
+
thread_local[:managers][self] = @manager
|
|
92
|
+
@manager.refresh! @seeds
|
|
93
|
+
else
|
|
94
|
+
@manager = ShardingPoolManager.new(self, @seeds)
|
|
95
|
+
ensure_manager
|
|
96
|
+
@manager.connect
|
|
97
|
+
check_wire_version_in_range
|
|
98
|
+
end
|
|
99
|
+
ensure
|
|
100
|
+
thread_local[:locks][:connecting] = false
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
@refresh_version += 1
|
|
104
|
+
@last_refresh = Time.now
|
|
105
|
+
@connected = true
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Force a hard refresh of this connection's view
|
|
110
|
+
# of the sharded cluster.
|
|
111
|
+
#
|
|
112
|
+
# @return [Boolean] +true+ if hard refresh
|
|
113
|
+
# occurred. +false+ is returned when unable
|
|
114
|
+
# to get the refresh lock.
|
|
115
|
+
def hard_refresh!
|
|
116
|
+
log(:info, "Initiating hard refresh...")
|
|
117
|
+
connect(true)
|
|
118
|
+
return true
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def connected?
|
|
122
|
+
!!(@connected && @manager.primary_pool)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Returns +true+ if it's okay to read from a secondary node.
|
|
126
|
+
# Since this is a sharded cluster, this must always be false.
|
|
127
|
+
#
|
|
128
|
+
# This method exist primarily so that Cursor objects will
|
|
129
|
+
# generate query messages with a slaveOkay value of +true+.
|
|
130
|
+
#
|
|
131
|
+
# @return [Boolean] +true+
|
|
132
|
+
def slave_ok?
|
|
133
|
+
false
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def checkout(&block)
|
|
137
|
+
tries = 0
|
|
138
|
+
begin
|
|
139
|
+
super(&block)
|
|
140
|
+
rescue ConnectionFailure
|
|
141
|
+
tries +=1
|
|
142
|
+
tries < 2 ? retry : raise
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Initialize a connection to MongoDB using the MongoDB URI spec.
|
|
147
|
+
#
|
|
148
|
+
# @param uri [ String ] string of the format:
|
|
149
|
+
# mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]
|
|
150
|
+
#
|
|
151
|
+
# @param options [ Hash ] Any of the options available for MongoShardedClient.new
|
|
152
|
+
#
|
|
153
|
+
# @return [ Mongo::MongoShardedClient ] The sharded client.
|
|
154
|
+
def self.from_uri(uri, options={})
|
|
155
|
+
uri ||= ENV['MONGODB_URI']
|
|
156
|
+
URIParser.new(uri).connection(options, false, true)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,370 @@
|
|
|
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
|
+
module Networking
|
|
17
|
+
|
|
18
|
+
STANDARD_HEADER_SIZE = 16
|
|
19
|
+
RESPONSE_HEADER_SIZE = 20
|
|
20
|
+
|
|
21
|
+
# Counter for generating unique request ids.
|
|
22
|
+
@@current_request_id = 0
|
|
23
|
+
|
|
24
|
+
# Send a message to MongoDB, adding the necessary headers.
|
|
25
|
+
#
|
|
26
|
+
# @param [Integer] operation a MongoDB opcode.
|
|
27
|
+
# @param [BSON::ByteBuffer] message a message to send to the database.
|
|
28
|
+
#
|
|
29
|
+
# @option opts [Symbol] :connection (:writer) The connection to which
|
|
30
|
+
# this message should be sent. Valid options are :writer and :reader.
|
|
31
|
+
#
|
|
32
|
+
# @return [Integer] number of bytes sent
|
|
33
|
+
def send_message(operation, message, opts={})
|
|
34
|
+
if opts.is_a?(String)
|
|
35
|
+
warn "MongoClient#send_message no longer takes a string log message. " +
|
|
36
|
+
"Logging is now handled within the Collection and Cursor classes."
|
|
37
|
+
opts = {}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
add_message_headers(message, operation)
|
|
41
|
+
packed_message = message.to_s
|
|
42
|
+
|
|
43
|
+
sock = nil
|
|
44
|
+
pool = opts.fetch(:pool, nil)
|
|
45
|
+
begin
|
|
46
|
+
if pool
|
|
47
|
+
#puts "send_message pool.port:#{pool.port}"
|
|
48
|
+
sock = pool.checkout
|
|
49
|
+
else
|
|
50
|
+
sock ||= checkout_writer
|
|
51
|
+
end
|
|
52
|
+
send_message_on_socket(packed_message, sock)
|
|
53
|
+
rescue SystemStackError, NoMemoryError, SystemCallError => ex
|
|
54
|
+
close
|
|
55
|
+
raise ex
|
|
56
|
+
ensure
|
|
57
|
+
if sock
|
|
58
|
+
sock.checkin
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Sends a message to the database, waits for a response, and raises
|
|
65
|
+
# an exception if the operation has failed.
|
|
66
|
+
#
|
|
67
|
+
# @param [Integer] operation a MongoDB opcode.
|
|
68
|
+
# @param [BSON::ByteBuffer] message a message to send to the database.
|
|
69
|
+
# @param [String] db_name the name of the database. used on call to get_last_error.
|
|
70
|
+
# @param [String] log_message this is currently a no-op and will be removed.
|
|
71
|
+
# @param [Hash] write_concern write concern.
|
|
72
|
+
#
|
|
73
|
+
# @see DB#get_last_error for valid last error params.
|
|
74
|
+
#
|
|
75
|
+
# @return [Hash] The document returned by the call to getlasterror.
|
|
76
|
+
def send_message_with_gle(operation, message, db_name, log_message=nil, write_concern=false)
|
|
77
|
+
docs = num_received = cursor_id = ''
|
|
78
|
+
add_message_headers(message, operation)
|
|
79
|
+
|
|
80
|
+
last_error_message = build_get_last_error_message(db_name, write_concern)
|
|
81
|
+
last_error_id = add_message_headers(last_error_message, Mongo::Constants::OP_QUERY)
|
|
82
|
+
|
|
83
|
+
packed_message = message.append!(last_error_message).to_s
|
|
84
|
+
sock = nil
|
|
85
|
+
begin
|
|
86
|
+
sock = checkout_writer
|
|
87
|
+
send_message_on_socket(packed_message, sock)
|
|
88
|
+
docs, num_received, cursor_id = receive(sock, last_error_id)
|
|
89
|
+
checkin(sock)
|
|
90
|
+
rescue ConnectionFailure, OperationFailure, OperationTimeout => ex
|
|
91
|
+
checkin(sock)
|
|
92
|
+
raise ex
|
|
93
|
+
rescue SystemStackError, NoMemoryError, SystemCallError => ex
|
|
94
|
+
close
|
|
95
|
+
raise ex
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if num_received == 1
|
|
99
|
+
error = docs[0]['err'] || docs[0]['errmsg']
|
|
100
|
+
if error && error.include?("not master")
|
|
101
|
+
close
|
|
102
|
+
raise ConnectionFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
|
|
103
|
+
elsif (note = docs[0]['jnote'] || docs[0]['wnote']) # assignment
|
|
104
|
+
code = docs[0]['code'] || Mongo::ErrorCode::BAD_VALUE # as of server version 2.5.5
|
|
105
|
+
raise WriteConcernError.new(code.to_s + ': ' + note, code, docs[0])
|
|
106
|
+
elsif error
|
|
107
|
+
code = docs[0]['code'] || Mongo::ErrorCode::UNKNOWN_ERROR
|
|
108
|
+
error = "wtimeout" if error == "timeout"
|
|
109
|
+
raise WriteConcernError.new(code.to_s + ': ' + error, code, docs[0]) if error == "wtimeout"
|
|
110
|
+
raise OperationFailure.new(code.to_s + ': ' + error, code, docs[0])
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
docs[0]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Sends a message to the database and waits for the response.
|
|
118
|
+
#
|
|
119
|
+
# @param [Integer] operation a MongoDB opcode.
|
|
120
|
+
# @param [BSON::ByteBuffer] message a message to send to the database.
|
|
121
|
+
# @param [String] log_message this is currently a no-op and will be removed.
|
|
122
|
+
# @param [Socket] socket a socket to use in lieu of checking out a new one.
|
|
123
|
+
# @param [Boolean] command (false) indicate whether this is a command. If this is a command,
|
|
124
|
+
# the message will be sent to the primary node.
|
|
125
|
+
# @param [Symbol] read the read preference.
|
|
126
|
+
# @param [Boolean] exhaust (false) indicate whether the cursor should be exhausted. Set
|
|
127
|
+
# this to true only when the OP_QUERY_EXHAUST flag is set.
|
|
128
|
+
# @param [Boolean] compile_regex whether BSON regex objects should be compiled into Ruby regexes.
|
|
129
|
+
#
|
|
130
|
+
# @return [Array]
|
|
131
|
+
# An array whose indexes include [0] documents returned, [1] number of document received,
|
|
132
|
+
# and [3] a cursor_id.
|
|
133
|
+
def receive_message(operation, message, log_message=nil, socket=nil, command=false,
|
|
134
|
+
read=:primary, exhaust=false, compile_regex=true)
|
|
135
|
+
request_id = add_message_headers(message, operation)
|
|
136
|
+
packed_message = message.to_s
|
|
137
|
+
opts = { :exhaust => exhaust,
|
|
138
|
+
:compile_regex => compile_regex }
|
|
139
|
+
|
|
140
|
+
result = ''
|
|
141
|
+
|
|
142
|
+
begin
|
|
143
|
+
send_message_on_socket(packed_message, socket)
|
|
144
|
+
result = receive(socket, request_id, opts)
|
|
145
|
+
rescue ConnectionFailure => ex
|
|
146
|
+
socket.close
|
|
147
|
+
checkin(socket)
|
|
148
|
+
raise ex
|
|
149
|
+
rescue SystemStackError, NoMemoryError, SystemCallError => ex
|
|
150
|
+
close
|
|
151
|
+
raise ex
|
|
152
|
+
rescue Exception => ex
|
|
153
|
+
if defined?(IRB)
|
|
154
|
+
close if ex.class == IRB::Abort
|
|
155
|
+
end
|
|
156
|
+
raise ex
|
|
157
|
+
end
|
|
158
|
+
result
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def receive(sock, cursor_id, opts={})
|
|
164
|
+
exhaust = !!opts.delete(:exhaust)
|
|
165
|
+
|
|
166
|
+
if exhaust
|
|
167
|
+
docs = []
|
|
168
|
+
num_received = 0
|
|
169
|
+
|
|
170
|
+
while(cursor_id != 0) do
|
|
171
|
+
receive_header(sock, cursor_id, exhaust)
|
|
172
|
+
number_received, cursor_id = receive_response_header(sock)
|
|
173
|
+
new_docs, n = read_documents(number_received, sock, opts)
|
|
174
|
+
docs += new_docs
|
|
175
|
+
num_received += n
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
return [docs, num_received, cursor_id]
|
|
179
|
+
else
|
|
180
|
+
receive_header(sock, cursor_id, exhaust)
|
|
181
|
+
number_received, cursor_id = receive_response_header(sock)
|
|
182
|
+
docs, num_received = read_documents(number_received, sock, opts)
|
|
183
|
+
|
|
184
|
+
return [docs, num_received, cursor_id]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def receive_header(sock, expected_response, exhaust=false)
|
|
189
|
+
header = receive_message_on_socket(16, sock)
|
|
190
|
+
|
|
191
|
+
# unpacks to size, request_id, response_to
|
|
192
|
+
response_to = header.unpack('VVV')[2]
|
|
193
|
+
if !exhaust && expected_response != response_to
|
|
194
|
+
raise Mongo::ConnectionFailure, "Expected response #{expected_response} but got #{response_to}"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
unless header.size == STANDARD_HEADER_SIZE
|
|
198
|
+
raise "Short read for DB response header: " +
|
|
199
|
+
"expected #{STANDARD_HEADER_SIZE} bytes, saw #{header.size}"
|
|
200
|
+
end
|
|
201
|
+
nil
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def receive_response_header(sock)
|
|
205
|
+
header_buf = receive_message_on_socket(RESPONSE_HEADER_SIZE, sock)
|
|
206
|
+
if header_buf.length != RESPONSE_HEADER_SIZE
|
|
207
|
+
raise "Short read for DB response header; " +
|
|
208
|
+
"expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# unpacks to flags, cursor_id_a, cursor_id_b, starting_from, number_remaining
|
|
212
|
+
flags, cursor_id_a, cursor_id_b, _, number_remaining = header_buf.unpack('VVVVV')
|
|
213
|
+
|
|
214
|
+
check_response_flags(flags)
|
|
215
|
+
cursor_id = (cursor_id_b << 32) + cursor_id_a
|
|
216
|
+
[number_remaining, cursor_id]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def check_response_flags(flags)
|
|
220
|
+
if flags & Mongo::Constants::REPLY_CURSOR_NOT_FOUND != 0
|
|
221
|
+
raise Mongo::OperationFailure, "Query response returned CURSOR_NOT_FOUND. " +
|
|
222
|
+
"Either an invalid cursor was specified, or the cursor may have timed out on the server."
|
|
223
|
+
elsif flags & Mongo::Constants::REPLY_QUERY_FAILURE != 0
|
|
224
|
+
# Mongo query reply failures are handled in Cursor#next.
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def read_documents(number_received, sock, opts)
|
|
229
|
+
docs = []
|
|
230
|
+
number_remaining = number_received
|
|
231
|
+
while number_remaining > 0 do
|
|
232
|
+
buf = receive_message_on_socket(4, sock)
|
|
233
|
+
size = buf.unpack('V')[0]
|
|
234
|
+
buf << receive_message_on_socket(size - 4, sock)
|
|
235
|
+
number_remaining -= 1
|
|
236
|
+
docs << BSON::BSON_CODER.deserialize(buf, opts)
|
|
237
|
+
end
|
|
238
|
+
[docs, number_received]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def build_command_message(db_name, query, projection=nil, skip=0, limit=-1)
|
|
242
|
+
message = BSON::ByteBuffer.new("", max_message_size)
|
|
243
|
+
message.put_int(0)
|
|
244
|
+
BSON::BSON_RUBY.serialize_cstr(message, "#{db_name}.$cmd")
|
|
245
|
+
message.put_int(skip)
|
|
246
|
+
message.put_int(limit)
|
|
247
|
+
message.put_binary(BSON::BSON_CODER.serialize(query, false, false, max_bson_size).to_s)
|
|
248
|
+
message.put_binary(BSON::BSON_CODER.serialize(projection, false, false, max_bson_size).to_s) if projection
|
|
249
|
+
message
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Constructs a getlasterror message. This method is used exclusively by
|
|
253
|
+
# MongoClient#send_message_with_gle.
|
|
254
|
+
def build_get_last_error_message(db_name, write_concern)
|
|
255
|
+
gle = BSON::OrderedHash.new
|
|
256
|
+
gle[:getlasterror] = 1
|
|
257
|
+
if write_concern.is_a?(Hash)
|
|
258
|
+
write_concern.assert_valid_keys(:w, :wtimeout, :fsync, :j)
|
|
259
|
+
gle.merge!(write_concern)
|
|
260
|
+
gle.delete(:w) if gle[:w] == 1
|
|
261
|
+
end
|
|
262
|
+
gle[:w] = gle[:w].to_s if gle[:w].is_a?(Symbol)
|
|
263
|
+
build_command_message(db_name, gle)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Prepares a message for transmission to MongoDB by
|
|
267
|
+
# constructing a valid message header.
|
|
268
|
+
#
|
|
269
|
+
# Note: this method modifies message by reference.
|
|
270
|
+
#
|
|
271
|
+
# @return [Integer] the request id used in the header
|
|
272
|
+
def add_message_headers(message, operation)
|
|
273
|
+
headers = [
|
|
274
|
+
# Message size.
|
|
275
|
+
16 + message.size,
|
|
276
|
+
|
|
277
|
+
# Unique request id.
|
|
278
|
+
request_id = get_request_id,
|
|
279
|
+
|
|
280
|
+
# Response id.
|
|
281
|
+
0,
|
|
282
|
+
|
|
283
|
+
# Opcode.
|
|
284
|
+
operation
|
|
285
|
+
].pack('VVVV')
|
|
286
|
+
|
|
287
|
+
message.prepend!(headers)
|
|
288
|
+
|
|
289
|
+
request_id
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Increment and return the next available request id.
|
|
293
|
+
#
|
|
294
|
+
# return [Integer]
|
|
295
|
+
def get_request_id
|
|
296
|
+
request_id = ''
|
|
297
|
+
@id_lock.synchronize do
|
|
298
|
+
request_id = @@current_request_id += 1
|
|
299
|
+
end
|
|
300
|
+
request_id
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Low-level method for sending a message on a socket.
|
|
304
|
+
# Requires a packed message and an available socket,
|
|
305
|
+
#
|
|
306
|
+
# @return [Integer] number of bytes sent
|
|
307
|
+
def send_message_on_socket(packed_message, socket)
|
|
308
|
+
begin
|
|
309
|
+
total_bytes_sent = socket.send(packed_message)
|
|
310
|
+
if total_bytes_sent != packed_message.size
|
|
311
|
+
packed_message.slice!(0, total_bytes_sent)
|
|
312
|
+
while packed_message.size > 0
|
|
313
|
+
byte_sent = socket.send(packed_message)
|
|
314
|
+
total_bytes_sent += byte_sent
|
|
315
|
+
packed_message.slice!(0, byte_sent)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
total_bytes_sent
|
|
319
|
+
rescue => ex
|
|
320
|
+
socket.close
|
|
321
|
+
raise ConnectionFailure, "Operation failed with the following exception: #{ex}:#{ex.message}"
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Low-level method for receiving data from socket.
|
|
326
|
+
# Requires length and an available socket.
|
|
327
|
+
def receive_message_on_socket(length, socket)
|
|
328
|
+
begin
|
|
329
|
+
message = receive_data(length, socket)
|
|
330
|
+
rescue OperationTimeout, ConnectionFailure => ex
|
|
331
|
+
socket.close
|
|
332
|
+
|
|
333
|
+
if ex.class == OperationTimeout
|
|
334
|
+
raise OperationTimeout, "Timed out waiting on socket read."
|
|
335
|
+
else
|
|
336
|
+
raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
message
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def receive_data(length, socket)
|
|
343
|
+
message = new_binary_string
|
|
344
|
+
socket.read(length, message)
|
|
345
|
+
|
|
346
|
+
raise ConnectionFailure, "connection closed" unless message && message.length > 0
|
|
347
|
+
if message.length < length
|
|
348
|
+
chunk = new_binary_string
|
|
349
|
+
while message.length < length
|
|
350
|
+
socket.read(length - message.length, chunk)
|
|
351
|
+
raise ConnectionFailure, "connection closed" unless chunk.length > 0
|
|
352
|
+
message << chunk
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
message
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
if defined?(Encoding)
|
|
359
|
+
BINARY_ENCODING = Encoding.find("binary")
|
|
360
|
+
|
|
361
|
+
def new_binary_string
|
|
362
|
+
"".force_encoding(BINARY_ENCODING)
|
|
363
|
+
end
|
|
364
|
+
else
|
|
365
|
+
def new_binary_string
|
|
366
|
+
""
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|