moped 1.2.9 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of moped might be problematic. Click here for more details.
- data/CHANGELOG.md +42 -0
- data/lib/moped.rb +1 -0
- data/lib/moped/cluster.rb +6 -4
- data/lib/moped/collection.rb +23 -0
- data/lib/moped/connection.rb +17 -107
- data/lib/moped/database.rb +1 -1
- data/lib/moped/mongo_uri.rb +149 -0
- data/lib/moped/node.rb +36 -23
- data/lib/moped/protocol/reply.rb +45 -1
- data/lib/moped/query.rb +15 -1
- data/lib/moped/session.rb +24 -1
- data/lib/moped/sockets/connectable.rb +114 -0
- data/lib/moped/sockets/ssl.rb +50 -0
- data/lib/moped/sockets/tcp.rb +23 -0
- data/lib/moped/version.rb +1 -1
- metadata +9 -5
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,47 @@
|
|
1
1
|
# Overview
|
2
2
|
|
3
|
+
## 1.3.0
|
4
|
+
|
5
|
+
### New Features
|
6
|
+
|
7
|
+
* \#114 Moped now accepts connecting with a URI. (Christopher Winslett)
|
8
|
+
|
9
|
+
Moped::Session.connect("mongodb://localhost:27017/my_db")
|
10
|
+
|
11
|
+
* \#79 Tailable cursors are now supported. These are just Ruby `Enumerators` that
|
12
|
+
keep the cursor open until the next document appears. The cursor will be closed
|
13
|
+
when it becomes "dead".
|
14
|
+
|
15
|
+
enumerator = session[:users].find.tailable.each
|
16
|
+
enumerator.next # Will stay open until next doc.
|
17
|
+
|
18
|
+
* mongoid/mongoid\#2460 Moped now makes the connection timeout configurable
|
19
|
+
by passing a `:timeout` option to the session. This defaults to 5 seconds.
|
20
|
+
|
21
|
+
Moped::Session.new([ "node1:27017", "node2:27017" ], timeout: 5)
|
22
|
+
|
23
|
+
* \#49 Support for the 2.2 aggregation framework is included. (Rodrigo Saito)
|
24
|
+
|
25
|
+
session[:users].aggregate({
|
26
|
+
"$group" => {
|
27
|
+
"_id" => "$city",
|
28
|
+
"totalpop" => { "$sum" => "$pop" }
|
29
|
+
}
|
30
|
+
})
|
31
|
+
|
32
|
+
* \#42 Moped now supports SSL connections to MongoDB. Provide the `ssl: true`
|
33
|
+
option when creating a new `Session`. This is currently experimental.
|
34
|
+
|
35
|
+
Moped::Session.new([ "ssl.mongohq.com:10004" ], ssl: true)
|
36
|
+
|
37
|
+
### Resolved Issues
|
38
|
+
|
39
|
+
* \#110 Handle timeout errors with SSL connections gracefully and mark nodes as
|
40
|
+
down without failing any other queries.
|
41
|
+
|
42
|
+
* \#109 Moped reauthorizes on "db assertion failures" with commands that have
|
43
|
+
an unauthorized assertion code in the reply.
|
44
|
+
|
3
45
|
## 1.2.9
|
4
46
|
|
5
47
|
* Moped now ensures that when reading bytes from the socket that it continues
|
data/lib/moped.rb
CHANGED
data/lib/moped/cluster.rb
CHANGED
@@ -93,18 +93,20 @@ module Moped
|
|
93
93
|
# to reconnect to a down node. (30)
|
94
94
|
# @option options :refresh_interval number of seconds to cache information
|
95
95
|
# about a node. (300)
|
96
|
+
# @option options [ Integer ] :timeout The time in seconds to wait for an
|
97
|
+
# operation to timeout. (5)
|
96
98
|
#
|
97
99
|
# @since 1.0.0
|
98
100
|
def initialize(hosts, options)
|
101
|
+
@seeds = hosts
|
102
|
+
@nodes = hosts.map { |host| Node.new(host, options) }
|
103
|
+
|
99
104
|
@options = {
|
100
105
|
down_interval: 30,
|
101
106
|
max_retries: 30,
|
102
107
|
refresh_interval: 300,
|
103
108
|
retry_interval: 1
|
104
109
|
}.merge(options)
|
105
|
-
|
106
|
-
@seeds = hosts
|
107
|
-
@nodes = hosts.map { |host| Node.new(host) }
|
108
110
|
end
|
109
111
|
|
110
112
|
# Returns the list of available nodes, refreshing 1) any nodes which were
|
@@ -169,7 +171,7 @@ module Moped
|
|
169
171
|
refreshed_nodes << node unless refreshed_nodes.include?(node)
|
170
172
|
|
171
173
|
# Now refresh any newly discovered peer nodes.
|
172
|
-
(node.peers - @nodes).each(&refresh_node)
|
174
|
+
(node.peers - @nodes).each(&refresh_node) if node.peers
|
173
175
|
rescue Errors::ConnectionFailure
|
174
176
|
# We couldn't connect to the node, so don't do anything with it.
|
175
177
|
end
|
data/lib/moped/collection.rb
CHANGED
@@ -91,5 +91,28 @@ module Moped
|
|
91
91
|
session.context.insert(database.name, name, documents, flags: flags || [])
|
92
92
|
end
|
93
93
|
end
|
94
|
+
|
95
|
+
# Call aggregate function over the collection.
|
96
|
+
#
|
97
|
+
# @example Execute an aggregation.
|
98
|
+
# session[:users].aggregate({
|
99
|
+
# "$group" => {
|
100
|
+
# "_id" => "$city",
|
101
|
+
# "totalpop" => { "$sum" => "$pop" }
|
102
|
+
# }
|
103
|
+
# })
|
104
|
+
#
|
105
|
+
# @param [ Hash, Array<Hash> ] documents representing the aggregate function to execute
|
106
|
+
#
|
107
|
+
# @return [ Hash ] containing the result of aggregation
|
108
|
+
#
|
109
|
+
# @since 1.3.0
|
110
|
+
def aggregate(pipeline)
|
111
|
+
pipeline = [ pipeline ] unless pipeline.is_a?(Array)
|
112
|
+
command = { aggregate: name.to_s, pipeline: pipeline }
|
113
|
+
database.session.with(consistency: :strong) do |sess|
|
114
|
+
sess.command(command)["result"]
|
115
|
+
end
|
116
|
+
end
|
94
117
|
end
|
95
118
|
end
|
data/lib/moped/connection.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
require "timeout"
|
2
|
+
require "moped/sockets/connectable"
|
3
|
+
require "moped/sockets/tcp"
|
4
|
+
require "moped/sockets/ssl"
|
2
5
|
|
3
6
|
module Moped
|
4
7
|
|
@@ -6,6 +9,9 @@ module Moped
|
|
6
9
|
#
|
7
10
|
# @api private
|
8
11
|
class Connection
|
12
|
+
|
13
|
+
attr_reader :host, :port, :timeout, :options
|
14
|
+
|
9
15
|
# Is the connection alive?
|
10
16
|
#
|
11
17
|
# @example Is the connection alive?
|
@@ -27,7 +33,11 @@ module Moped
|
|
27
33
|
#
|
28
34
|
# @since 1.0.0
|
29
35
|
def connect
|
30
|
-
|
36
|
+
@sock = if !!options[:ssl]
|
37
|
+
Sockets::SSL.connect(host, port, timeout)
|
38
|
+
else
|
39
|
+
Sockets::TCP.connect(host, port, timeout)
|
40
|
+
end
|
31
41
|
end
|
32
42
|
|
33
43
|
# Is the connection connected?
|
@@ -65,13 +75,14 @@ module Moped
|
|
65
75
|
# @param [ String ] host The host to connect to.
|
66
76
|
# @param [ Integer ] post The server port.
|
67
77
|
# @param [ Integer ] timeout The connection timeout.
|
78
|
+
# @param [ Hash ] options Options for the connection.
|
79
|
+
#
|
80
|
+
# @option options [ Boolean ] :ssl Connect using SSL
|
68
81
|
# @since 1.0.0
|
69
|
-
def initialize(host, port, timeout)
|
82
|
+
def initialize(host, port, timeout, options = {})
|
70
83
|
@sock = nil
|
71
84
|
@request_id = 0
|
72
|
-
@host = host
|
73
|
-
@port = port
|
74
|
-
@timeout = timeout
|
85
|
+
@host, @port, @timeout, @options = host, port, timeout, options
|
75
86
|
end
|
76
87
|
|
77
88
|
# Read from the connection.
|
@@ -148,10 +159,6 @@ module Moped
|
|
148
159
|
|
149
160
|
private
|
150
161
|
|
151
|
-
def create_connection
|
152
|
-
@sock = TCPSocket.connect @host, @port, @timeout
|
153
|
-
end
|
154
|
-
|
155
162
|
# Read data from the socket until we get back the number of bytes that we
|
156
163
|
# are expecting.
|
157
164
|
#
|
@@ -191,105 +198,8 @@ module Moped
|
|
191
198
|
#
|
192
199
|
# @since 1.3.0
|
193
200
|
def with_connection
|
194
|
-
|
201
|
+
connect if @sock.nil? || !@sock.alive?
|
195
202
|
yield @sock
|
196
203
|
end
|
197
|
-
|
198
|
-
# This is a wrapper around a tcp socket.
|
199
|
-
class TCPSocket < ::TCPSocket
|
200
|
-
attr_reader :host, :port
|
201
|
-
|
202
|
-
# Is the socket connection alive?
|
203
|
-
#
|
204
|
-
# @example Is the socket alive?
|
205
|
-
# socket.alive?
|
206
|
-
#
|
207
|
-
# @return [ true, false ] If the socket is alive.
|
208
|
-
#
|
209
|
-
# @since 1.0.0
|
210
|
-
def alive?
|
211
|
-
if Kernel::select([ self ], nil, nil, 0)
|
212
|
-
!eof? rescue false
|
213
|
-
else
|
214
|
-
true
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
# Initialize the new TCPSocket.
|
219
|
-
#
|
220
|
-
# @example Initialize the socket.
|
221
|
-
# TCPSocket.new("127.0.0.1", 27017)
|
222
|
-
#
|
223
|
-
# @param [ String ] host The host.
|
224
|
-
# @param [ Integer ] port The port.
|
225
|
-
#
|
226
|
-
# @since 1.2.0
|
227
|
-
def initialize(host, port, *args)
|
228
|
-
@host, @port = host, port
|
229
|
-
handle_socket_errors { super }
|
230
|
-
end
|
231
|
-
|
232
|
-
# Read from the TCP socket.
|
233
|
-
#
|
234
|
-
# @param [ Integer ] length The length to read.
|
235
|
-
#
|
236
|
-
# @return [ Object ] The data.
|
237
|
-
#
|
238
|
-
# @since 1.2.0
|
239
|
-
def read(length)
|
240
|
-
handle_socket_errors { super }
|
241
|
-
end
|
242
|
-
|
243
|
-
# Write to the socket.
|
244
|
-
#
|
245
|
-
# @example Write to the socket.
|
246
|
-
# socket.write(data)
|
247
|
-
#
|
248
|
-
# @param [ Object ] args The data to write.
|
249
|
-
#
|
250
|
-
# @return [ Integer ] The number of bytes written.
|
251
|
-
#
|
252
|
-
# @since 1.0.0
|
253
|
-
def write(*args)
|
254
|
-
raise Errors::ConnectionFailure, "Socket connection was closed by remote host" unless alive?
|
255
|
-
handle_socket_errors { super }
|
256
|
-
end
|
257
|
-
|
258
|
-
private
|
259
|
-
|
260
|
-
def handle_socket_errors
|
261
|
-
yield
|
262
|
-
rescue Timeout::Error
|
263
|
-
raise Errors::ConnectionFailure, "Timed out connection to Mongo on #{host}:#{port}"
|
264
|
-
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EPIPE
|
265
|
-
raise Errors::ConnectionFailure, "Could not connect to Mongo on #{host}:#{port}"
|
266
|
-
rescue Errno::ECONNRESET
|
267
|
-
raise Errors::ConnectionFailure, "Connection reset to Mongo on #{host}:#{port}"
|
268
|
-
end
|
269
|
-
|
270
|
-
class << self
|
271
|
-
|
272
|
-
# Connect to the tcp server.
|
273
|
-
#
|
274
|
-
# @example Connect to the server.
|
275
|
-
# TCPSocket.connect("127.0.0.1", 27017, 30)
|
276
|
-
#
|
277
|
-
# @param [ String ] host The host to connect to.
|
278
|
-
# @param [ Integer ] post The server port.
|
279
|
-
# @param [ Integer ] timeout The connection timeout.
|
280
|
-
#
|
281
|
-
# @return [ TCPSocket ] The socket.
|
282
|
-
#
|
283
|
-
# @since 1.0.0
|
284
|
-
def connect(host, port, timeout)
|
285
|
-
Timeout::timeout(timeout) do
|
286
|
-
sock = new(host, port)
|
287
|
-
sock.set_encoding('binary')
|
288
|
-
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
289
|
-
sock
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
end
|
294
204
|
end
|
295
205
|
end
|
data/lib/moped/database.rb
CHANGED
@@ -0,0 +1,149 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Moped
|
3
|
+
|
4
|
+
# Parses MongoDB uri
|
5
|
+
#
|
6
|
+
# @api public
|
7
|
+
class MongoUri
|
8
|
+
|
9
|
+
SCHEME = /(mongodb:\/\/)/
|
10
|
+
USER = /([-.\w:]+)/
|
11
|
+
PASS = /([^@,]+)/
|
12
|
+
NODES = /((([-.\w]+)(?::(\w+))?,?)+)/
|
13
|
+
DATABASE = /(?:\/([-\w]+))?/
|
14
|
+
OPTIONS = /(?:\?(.+))/
|
15
|
+
|
16
|
+
URI = /#{SCHEME}(#{USER}:#{PASS}@)?#{NODES}#{DATABASE}#{OPTIONS}?/
|
17
|
+
|
18
|
+
attr_reader :match
|
19
|
+
|
20
|
+
# Helper to determine if authentication is provided
|
21
|
+
#
|
22
|
+
# @example Boolean response if username/password given
|
23
|
+
# uri.auth_provided?
|
24
|
+
#
|
25
|
+
# @return [ true, false ] If authorization is provided.
|
26
|
+
#
|
27
|
+
# @since 1.3.0
|
28
|
+
def auth_provided?
|
29
|
+
!username.nil? && !password.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get the database provided in the URI.
|
33
|
+
#
|
34
|
+
# @example Get the database.
|
35
|
+
# uri.database
|
36
|
+
#
|
37
|
+
# @return [ String ] The database.
|
38
|
+
#
|
39
|
+
# @since 1.3.0
|
40
|
+
def database
|
41
|
+
@database ||= match[9]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get the hosts provided in the URI.
|
45
|
+
#
|
46
|
+
# @example Get the hosts.
|
47
|
+
# uri.hosts
|
48
|
+
#
|
49
|
+
# @return [ Array<String> ] The hosts.
|
50
|
+
#
|
51
|
+
# @since 1.3.0
|
52
|
+
def hosts
|
53
|
+
@hosts ||= match[5].split(",")
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create the new uri from the provided string.
|
57
|
+
#
|
58
|
+
# @example Create the new uri.
|
59
|
+
# MongoUri.new(uri)
|
60
|
+
#
|
61
|
+
# @param [ String ] string The uri string.
|
62
|
+
#
|
63
|
+
# @since 1.3.0
|
64
|
+
def initialize(string)
|
65
|
+
@match = string.match(URI)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get the options provided in the URI.
|
69
|
+
# @example Get the options
|
70
|
+
# uri.options
|
71
|
+
#
|
72
|
+
# @return [ Hash ] Options hash usable by Moped
|
73
|
+
#
|
74
|
+
# @since 1.3.0
|
75
|
+
def options
|
76
|
+
options_string, options = @match[10], {database: database}
|
77
|
+
|
78
|
+
unless options_string.nil?
|
79
|
+
options_string.split(/\&/).each do |option_string|
|
80
|
+
key, value = option_string.split(/=/)
|
81
|
+
|
82
|
+
if value == "true"
|
83
|
+
options[key.to_sym] = true
|
84
|
+
elsif value == "false"
|
85
|
+
options[key.to_sym] = false
|
86
|
+
elsif value =~ /[\d]/
|
87
|
+
options[key.to_sym] = value.to_i
|
88
|
+
else
|
89
|
+
options[key.to_sym] = value.to_sym
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
options
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get the password provided in the URI.
|
98
|
+
#
|
99
|
+
# @example Get the password.
|
100
|
+
# uri.password
|
101
|
+
#
|
102
|
+
# @return [ String ] The password.
|
103
|
+
#
|
104
|
+
# @since 1.3.0
|
105
|
+
def password
|
106
|
+
@password ||= match[4]
|
107
|
+
end
|
108
|
+
|
109
|
+
# Get the uri as a Mongoid friendly configuration hash.
|
110
|
+
#
|
111
|
+
# @example Get the uri as a hash.
|
112
|
+
# uri.to_hash
|
113
|
+
#
|
114
|
+
# @return [ Hash ] The uri as options.
|
115
|
+
#
|
116
|
+
# @since 1.3.0
|
117
|
+
def to_hash
|
118
|
+
config = { database: database, hosts: hosts }
|
119
|
+
if username && password
|
120
|
+
config.merge!(username: username, password: password)
|
121
|
+
end
|
122
|
+
config
|
123
|
+
end
|
124
|
+
|
125
|
+
# Create Moped usable arguments
|
126
|
+
#
|
127
|
+
# @example Get the moped args
|
128
|
+
# uri.moped_arguments
|
129
|
+
#
|
130
|
+
# @return [ Array ] Array of arguments usable by Moped
|
131
|
+
#
|
132
|
+
# @since 1.3.0
|
133
|
+
def moped_arguments
|
134
|
+
[hosts, options]
|
135
|
+
end
|
136
|
+
|
137
|
+
# Get the username provided in the URI.
|
138
|
+
#
|
139
|
+
# @example Get the username.
|
140
|
+
# uri.username
|
141
|
+
#
|
142
|
+
# @return [ String ] The username.
|
143
|
+
#
|
144
|
+
# @since 1.3.0
|
145
|
+
def username
|
146
|
+
@username ||= match[3]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/moped/node.rb
CHANGED
@@ -12,7 +12,8 @@ module Moped
|
|
12
12
|
# @attribute [r] port The connection port.
|
13
13
|
# @attribute [r] resolved_address The host/port pair.
|
14
14
|
# @attribute [r] timeout The connection timeout.
|
15
|
-
|
15
|
+
# @attribute [r] options Additional options for the node (ssl).
|
16
|
+
attr_reader :address, :down_at, :ip_address, :peers, :port, :resolved_address, :timeout, :options
|
16
17
|
|
17
18
|
# Is this node equal to another?
|
18
19
|
#
|
@@ -68,10 +69,15 @@ module Moped
|
|
68
69
|
operation = Protocol::Command.new(database, cmd, options)
|
69
70
|
|
70
71
|
process(operation) do |reply|
|
71
|
-
result = reply.documents
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
result = reply.documents.first
|
73
|
+
if reply.command_failure?
|
74
|
+
if reply.unauthorized? && auth.has_key?(database)
|
75
|
+
login(database, *auth[database])
|
76
|
+
command(database, cmd, options)
|
77
|
+
else
|
78
|
+
raise Errors::OperationFailure.new(operation, result)
|
79
|
+
end
|
80
|
+
end
|
75
81
|
result
|
76
82
|
end
|
77
83
|
end
|
@@ -222,11 +228,13 @@ module Moped
|
|
222
228
|
# Node.new("127.0.0.1:27017")
|
223
229
|
#
|
224
230
|
# @param [ String ] address The location of the server node.
|
231
|
+
# @param [ Hash ] options Additional options for the node (ssl)
|
225
232
|
#
|
226
233
|
# @since 1.0.0
|
227
|
-
def initialize(address)
|
234
|
+
def initialize(address, options = {})
|
228
235
|
@address = address
|
229
|
-
@
|
236
|
+
@options = options
|
237
|
+
@timeout = options[:timeout] || 5
|
230
238
|
@down_at = nil
|
231
239
|
@refreshed_at = nil
|
232
240
|
@primary = nil
|
@@ -387,25 +395,30 @@ module Moped
|
|
387
395
|
# @since 1.0.0
|
388
396
|
def refresh
|
389
397
|
if resolve_address
|
390
|
-
|
398
|
+
begin
|
399
|
+
info = command("admin", ismaster: 1)
|
391
400
|
|
392
|
-
|
393
|
-
|
394
|
-
|
401
|
+
@refreshed_at = Time.now
|
402
|
+
primary = true if info["ismaster"]
|
403
|
+
secondary = true if info["secondary"]
|
395
404
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
405
|
+
peers = []
|
406
|
+
peers.push(info["primary"]) if info["primary"]
|
407
|
+
peers.concat(info["hosts"]) if info["hosts"]
|
408
|
+
peers.concat(info["passives"]) if info["passives"]
|
409
|
+
peers.concat(info["arbiters"]) if info["arbiters"]
|
401
410
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
411
|
+
@peers = peers.map { |peer| Node.new(peer, options) }
|
412
|
+
@primary, @secondary = primary, secondary
|
413
|
+
@arbiter = info["arbiterOnly"]
|
414
|
+
@passive = info["passive"]
|
406
415
|
|
407
|
-
|
408
|
-
|
416
|
+
if !primary && Threaded.executing?(:ensure_primary)
|
417
|
+
raise Errors::ReplicaSetReconfigured, "#{inspect} is no longer the primary node."
|
418
|
+
end
|
419
|
+
rescue Timeout::Error
|
420
|
+
@peers = []
|
421
|
+
down!
|
409
422
|
end
|
410
423
|
end
|
411
424
|
end
|
@@ -496,7 +509,7 @@ module Moped
|
|
496
509
|
end
|
497
510
|
|
498
511
|
def connection
|
499
|
-
@connection ||= Connection.new(ip_address, port, timeout)
|
512
|
+
@connection ||= Connection.new(ip_address, port, timeout, options)
|
500
513
|
end
|
501
514
|
|
502
515
|
def connected?
|
data/lib/moped/protocol/reply.rb
CHANGED
@@ -55,16 +55,60 @@ module Moped
|
|
55
55
|
|
56
56
|
finalize
|
57
57
|
|
58
|
+
# Is the reply the result of a command failure?
|
59
|
+
#
|
60
|
+
# @example Did the command fail?
|
61
|
+
# reply.command_failure?
|
62
|
+
#
|
63
|
+
# @note This is when ok is not 1, or "err" or "errmsg" are present.
|
64
|
+
#
|
65
|
+
# @return [ true, false ] If the command failed.
|
66
|
+
#
|
67
|
+
# @since 1.2.10
|
68
|
+
def command_failure?
|
69
|
+
result = documents[0]
|
70
|
+
result["ok"] != 1 || result["err"] || result["errmsg"]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Was the provided cursor id not found on the server?
|
74
|
+
#
|
75
|
+
# @example Is the cursor not on the server?
|
76
|
+
# reply.cursor_not_found?
|
77
|
+
#
|
78
|
+
# @return [ true, false ] If the cursor went missing.
|
79
|
+
#
|
80
|
+
# @since 1.2.0
|
58
81
|
def cursor_not_found?
|
59
82
|
flags.include?(:cursor_not_found)
|
60
83
|
end
|
61
84
|
|
85
|
+
# Did the query fail on the server?
|
86
|
+
#
|
87
|
+
# @example Did the query fail?
|
88
|
+
# reply.query_failed?
|
89
|
+
#
|
90
|
+
# @return [ true, false ] If the query failed.
|
91
|
+
#
|
92
|
+
# @since 1.2.0
|
62
93
|
def query_failed?
|
63
94
|
flags.include?(:query_failure)
|
64
95
|
end
|
65
96
|
|
97
|
+
# Is the reply an error message that we are not authorized for the query
|
98
|
+
# or command?
|
99
|
+
#
|
100
|
+
# @example Was the query unauthorized.
|
101
|
+
# reply.unauthorized?
|
102
|
+
#
|
103
|
+
# @note So far this can be a "code" of 10057 in the error message or an
|
104
|
+
# "assertionCode" of 10057.
|
105
|
+
#
|
106
|
+
# @return [ true, false ] If we had an authorization error.
|
107
|
+
#
|
108
|
+
# @since 1.2.10
|
66
109
|
def unauthorized?
|
67
|
-
documents
|
110
|
+
result = documents[0]
|
111
|
+
result["code"] == UNAUTHORIZED || result["assertionCode"] == UNAUTHORIZED
|
68
112
|
end
|
69
113
|
|
70
114
|
class << self
|
data/lib/moped/query.rb
CHANGED
@@ -79,6 +79,7 @@ module Moped
|
|
79
79
|
end if block_given?
|
80
80
|
enum
|
81
81
|
end
|
82
|
+
alias :cursor :each
|
82
83
|
|
83
84
|
# Explain the current query.
|
84
85
|
#
|
@@ -211,7 +212,7 @@ module Moped
|
|
211
212
|
end
|
212
213
|
|
213
214
|
# Keeping moped compatibility with mongodb >= 2.2.0-rc0
|
214
|
-
options[:upsert] && !result ?
|
215
|
+
options[:upsert] && !result ? {} : result
|
215
216
|
end
|
216
217
|
|
217
218
|
# Remove a single document matching the query's selector.
|
@@ -296,6 +297,19 @@ module Moped
|
|
296
297
|
self
|
297
298
|
end
|
298
299
|
|
300
|
+
# Tell the query to create a tailable cursor.
|
301
|
+
#
|
302
|
+
# @example Tell the query the cursor is tailable.
|
303
|
+
# db[:people].find.tailable
|
304
|
+
#
|
305
|
+
# @return [ Query ] The query.
|
306
|
+
#
|
307
|
+
# @since 1.3.0
|
308
|
+
def tailable
|
309
|
+
operation.flags.push(:tailable)
|
310
|
+
self
|
311
|
+
end
|
312
|
+
|
299
313
|
# Update a single document matching the query's selector.
|
300
314
|
#
|
301
315
|
# @example Update the first matching document.
|
data/lib/moped/session.rb
CHANGED
@@ -177,14 +177,17 @@ module Moped
|
|
177
177
|
# specified safety level e.g., "fsync: true", or "w: 2, wtimeout: 5".
|
178
178
|
# @option options [ Symbol, String ] :database The database to use.
|
179
179
|
# @option options [ :strong, :eventual ] :consistency (:eventual).
|
180
|
+
# @option options [ Boolean ] :ssl Connect using SSL.
|
180
181
|
# @option options [ Integer ] :max_retries The maximum number of attempts
|
181
182
|
# to retry an operation. (30)
|
182
183
|
# @option options [ Integer ] :retry_interval The time in seconds to retry
|
183
184
|
# connections to a secondary or primary after a failure. (1)
|
185
|
+
# @option options [ Integer ] :timeout The time in seconds to wait for an
|
186
|
+
# operation to timeout. (5)
|
184
187
|
#
|
185
188
|
# @since 1.0.0
|
186
189
|
def initialize(seeds, options = {})
|
187
|
-
@cluster = Cluster.new(seeds,
|
190
|
+
@cluster = Cluster.new(seeds, options)
|
188
191
|
@context = Context.new(self)
|
189
192
|
@options = options
|
190
193
|
@options[:consistency] ||= :eventual
|
@@ -312,6 +315,26 @@ module Moped
|
|
312
315
|
end
|
313
316
|
end
|
314
317
|
|
318
|
+
class << self
|
319
|
+
|
320
|
+
# Create a new session from a URI.
|
321
|
+
#
|
322
|
+
# @example Initialize a new session.
|
323
|
+
# Session.connect("mongodb://localhost:27017/my_db")
|
324
|
+
#
|
325
|
+
# @param [ String ] MongoDB URI formatted string.
|
326
|
+
#
|
327
|
+
# @return [ Session ] The new session.
|
328
|
+
#
|
329
|
+
# @since 3.0.0
|
330
|
+
def connect(uri)
|
331
|
+
uri = MongoUri.new(uri)
|
332
|
+
session = new(*uri.moped_arguments)
|
333
|
+
session.login(uri.username, uri.password) if uri.auth_provided?
|
334
|
+
session
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
315
338
|
private
|
316
339
|
|
317
340
|
def current_database
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Moped
|
2
|
+
module Sockets
|
3
|
+
module Connectable
|
4
|
+
|
5
|
+
attr_reader :host, :port
|
6
|
+
|
7
|
+
# Is the socket connection alive?
|
8
|
+
#
|
9
|
+
# @example Is the socket alive?
|
10
|
+
# socket.alive?
|
11
|
+
#
|
12
|
+
# @return [ true, false ] If the socket is alive.
|
13
|
+
#
|
14
|
+
# @since 1.0.0
|
15
|
+
def alive?
|
16
|
+
if Kernel::select([ self ], nil, nil, 0)
|
17
|
+
!eof? rescue false
|
18
|
+
else
|
19
|
+
true
|
20
|
+
end
|
21
|
+
rescue IOError
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
# Bring in the class methods when included.
|
26
|
+
#
|
27
|
+
# @example Extend the class methods.
|
28
|
+
# Connectable.included(class)
|
29
|
+
#
|
30
|
+
# @param [ Class ] klass The class including the module.
|
31
|
+
#
|
32
|
+
# @since 1.3.0
|
33
|
+
def self.included(klass)
|
34
|
+
klass.send(:extend, ClassMethods)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Read from the TCP socket.
|
38
|
+
#
|
39
|
+
# @param [ Integer ] length The length to read.
|
40
|
+
#
|
41
|
+
# @return [ Object ] The data.
|
42
|
+
#
|
43
|
+
# @since 1.2.0
|
44
|
+
def read(length)
|
45
|
+
handle_socket_errors { super }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Write to the socket.
|
49
|
+
#
|
50
|
+
# @example Write to the socket.
|
51
|
+
# socket.write(data)
|
52
|
+
#
|
53
|
+
# @param [ Object ] args The data to write.
|
54
|
+
#
|
55
|
+
# @return [ Integer ] The number of bytes written.
|
56
|
+
#
|
57
|
+
# @since 1.0.0
|
58
|
+
def write(*args)
|
59
|
+
raise Errors::ConnectionFailure, "Socket connection was closed by remote host" unless alive?
|
60
|
+
handle_socket_errors { super }
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Handle the potential socket errors that can occur.
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
# @example Handle the socket errors while executing the block.
|
70
|
+
# handle_socket_errors do
|
71
|
+
# #...
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# @return [ Object ] The result of the yield.
|
75
|
+
#
|
76
|
+
# @since 1.0.0
|
77
|
+
def handle_socket_errors
|
78
|
+
yield
|
79
|
+
rescue Timeout::Error
|
80
|
+
raise Errors::ConnectionFailure, "Timed out connection to Mongo on #{host}:#{port}"
|
81
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EPIPE
|
82
|
+
raise Errors::ConnectionFailure, "Could not connect to Mongo on #{host}:#{port}"
|
83
|
+
rescue Errno::ECONNRESET
|
84
|
+
raise Errors::ConnectionFailure, "Connection reset to Mongo on #{host}:#{port}"
|
85
|
+
rescue OpenSSL::SSL::SSLError => e
|
86
|
+
raise Errors::ConnectionFailure, "SSL Error '#{e.to_s}' for connection to Mongo on #{host}:#{port}"
|
87
|
+
end
|
88
|
+
|
89
|
+
module ClassMethods
|
90
|
+
|
91
|
+
# Connect to the tcp server.
|
92
|
+
#
|
93
|
+
# @example Connect to the server.
|
94
|
+
# TCPSocket.connect("127.0.0.1", 27017, 30)
|
95
|
+
#
|
96
|
+
# @param [ String ] host The host to connect to.
|
97
|
+
# @param [ Integer ] post The server port.
|
98
|
+
# @param [ Integer ] timeout The connection timeout.
|
99
|
+
#
|
100
|
+
# @return [ TCPSocket ] The socket.
|
101
|
+
#
|
102
|
+
# @since 1.0.0
|
103
|
+
def connect(host, port, timeout)
|
104
|
+
Timeout::timeout(timeout) do
|
105
|
+
sock = new(host, port)
|
106
|
+
sock.set_encoding('binary')
|
107
|
+
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
108
|
+
sock
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Moped
|
4
|
+
module Sockets
|
5
|
+
|
6
|
+
# This is a wrapper around a tcp socket.
|
7
|
+
class SSL < OpenSSL::SSL::SSLSocket
|
8
|
+
include Connectable
|
9
|
+
|
10
|
+
attr_reader :socket
|
11
|
+
|
12
|
+
# Initialize the new TCPSocket with SSL.
|
13
|
+
#
|
14
|
+
# @example Initialize the socket.
|
15
|
+
# SSL.new("127.0.0.1", 27017)
|
16
|
+
#
|
17
|
+
# @param [ String ] host The host.
|
18
|
+
# @param [ Integer ] port The port.
|
19
|
+
#
|
20
|
+
# @since 1.2.0
|
21
|
+
def initialize(host, port)
|
22
|
+
@host, @port = host, port
|
23
|
+
handle_socket_errors do
|
24
|
+
@socket = TCPSocket.new(host, port)
|
25
|
+
super(socket)
|
26
|
+
self.sync_close = true
|
27
|
+
connect
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set the encoding of the underlying socket.
|
32
|
+
#
|
33
|
+
# @param [ String ] string The encoding.
|
34
|
+
#
|
35
|
+
# @since 1.3.0
|
36
|
+
def set_encoding(string)
|
37
|
+
socket.set_encoding(string)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set a socket option on the underlying socket.
|
41
|
+
#
|
42
|
+
# @param [ Array<Object> ] args The option arguments.
|
43
|
+
#
|
44
|
+
# @since 1.3.0
|
45
|
+
def setsockopt(*args)
|
46
|
+
socket.setsockopt(*args)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Moped
|
2
|
+
module Sockets
|
3
|
+
|
4
|
+
# This is a wrapper around a tcp socket.
|
5
|
+
class TCP < ::TCPSocket
|
6
|
+
include Connectable
|
7
|
+
|
8
|
+
# Initialize the new TCPSocket.
|
9
|
+
#
|
10
|
+
# @example Initialize the socket.
|
11
|
+
# TCPSocket.new("127.0.0.1", 27017)
|
12
|
+
#
|
13
|
+
# @param [ String ] host The host.
|
14
|
+
# @param [ Integer ] port The port.
|
15
|
+
#
|
16
|
+
# @since 1.2.0
|
17
|
+
def initialize(host, port)
|
18
|
+
@host, @port = host, port
|
19
|
+
handle_socket_errors { super }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/moped/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: moped
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-18 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A MongoDB driver for Ruby.
|
15
15
|
email:
|
@@ -49,6 +49,7 @@ files:
|
|
49
49
|
- lib/moped/errors.rb
|
50
50
|
- lib/moped/indexes.rb
|
51
51
|
- lib/moped/logging.rb
|
52
|
+
- lib/moped/mongo_uri.rb
|
52
53
|
- lib/moped/node.rb
|
53
54
|
- lib/moped/protocol/command.rb
|
54
55
|
- lib/moped/protocol/commands/authenticate.rb
|
@@ -65,13 +66,16 @@ files:
|
|
65
66
|
- lib/moped/query.rb
|
66
67
|
- lib/moped/session/context.rb
|
67
68
|
- lib/moped/session.rb
|
69
|
+
- lib/moped/sockets/connectable.rb
|
70
|
+
- lib/moped/sockets/ssl.rb
|
71
|
+
- lib/moped/sockets/tcp.rb
|
68
72
|
- lib/moped/threaded.rb
|
69
73
|
- lib/moped/version.rb
|
70
74
|
- lib/moped.rb
|
71
75
|
- CHANGELOG.md
|
72
76
|
- LICENSE
|
73
77
|
- README.md
|
74
|
-
homepage: http://mongoid.org/moped
|
78
|
+
homepage: http://mongoid.org/en/moped
|
75
79
|
licenses: []
|
76
80
|
post_install_message:
|
77
81
|
rdoc_options: []
|
@@ -85,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
89
|
version: '0'
|
86
90
|
segments:
|
87
91
|
- 0
|
88
|
-
hash:
|
92
|
+
hash: 2772381747018055837
|
89
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
94
|
none: false
|
91
95
|
requirements:
|
@@ -94,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
98
|
version: '0'
|
95
99
|
segments:
|
96
100
|
- 0
|
97
|
-
hash:
|
101
|
+
hash: 2772381747018055837
|
98
102
|
requirements: []
|
99
103
|
rubyforge_project:
|
100
104
|
rubygems_version: 1.8.24
|