mongodb-mongo 0.12 → 0.13
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.
- data/README.rdoc +12 -12
- data/Rakefile +1 -1
- data/bin/bson_benchmark.rb +1 -1
- data/bin/mongo_console +3 -3
- data/bin/run_test_script +2 -2
- data/bin/standard_benchmark +3 -3
- data/examples/admin.rb +3 -3
- data/examples/benchmarks.rb +2 -2
- data/examples/blog.rb +4 -4
- data/examples/capped.rb +3 -3
- data/examples/cursor.rb +3 -3
- data/examples/gridfs.rb +4 -4
- data/examples/index_test.rb +11 -11
- data/examples/info.rb +3 -3
- data/examples/queries.rb +3 -3
- data/examples/simple.rb +3 -3
- data/examples/strict.rb +3 -3
- data/examples/types.rb +4 -9
- data/lib/mongo.rb +35 -3
- data/lib/mongo/admin.rb +56 -60
- data/lib/mongo/collection.rb +368 -320
- data/lib/mongo/connection.rb +166 -0
- data/lib/mongo/cursor.rb +206 -209
- data/lib/mongo/db.rb +478 -489
- data/lib/mongo/errors.rb +8 -9
- data/lib/mongo/gridfs/chunk.rb +66 -70
- data/lib/mongo/gridfs/grid_store.rb +406 -410
- data/lib/mongo/message/get_more_message.rb +8 -13
- data/lib/mongo/message/insert_message.rb +7 -11
- data/lib/mongo/message/kill_cursors_message.rb +7 -12
- data/lib/mongo/message/message.rb +58 -62
- data/lib/mongo/message/message_header.rb +19 -24
- data/lib/mongo/message/msg_message.rb +5 -9
- data/lib/mongo/message/opcodes.rb +10 -15
- data/lib/mongo/message/query_message.rb +42 -46
- data/lib/mongo/message/remove_message.rb +8 -12
- data/lib/mongo/message/update_message.rb +9 -13
- data/lib/mongo/query.rb +84 -88
- data/lib/mongo/types/binary.rb +13 -17
- data/lib/mongo/types/code.rb +9 -13
- data/lib/mongo/types/dbref.rb +10 -14
- data/lib/mongo/types/objectid.rb +103 -107
- data/lib/mongo/types/regexp_of_holding.rb +18 -22
- data/lib/mongo/types/undefined.rb +7 -10
- data/lib/mongo/util/bson.rb +4 -9
- data/lib/mongo/util/xml_to_ruby.rb +1 -3
- data/mongo-ruby-driver.gemspec +33 -32
- data/{tests → test}/mongo-qa/_common.rb +1 -1
- data/{tests → test}/mongo-qa/admin +1 -1
- data/{tests → test}/mongo-qa/capped +1 -1
- data/{tests → test}/mongo-qa/count1 +4 -4
- data/{tests → test}/mongo-qa/dbs +1 -1
- data/{tests → test}/mongo-qa/find +1 -1
- data/{tests → test}/mongo-qa/find1 +1 -1
- data/{tests → test}/mongo-qa/gridfs_in +2 -2
- data/{tests → test}/mongo-qa/gridfs_out +2 -2
- data/{tests → test}/mongo-qa/indices +2 -2
- data/{tests → test}/mongo-qa/remove +1 -1
- data/{tests → test}/mongo-qa/stress1 +1 -1
- data/{tests → test}/mongo-qa/test1 +1 -1
- data/{tests → test}/mongo-qa/update +1 -1
- data/{tests → test}/test_admin.rb +3 -3
- data/{tests → test}/test_bson.rb +4 -4
- data/{tests → test}/test_byte_buffer.rb +0 -0
- data/{tests → test}/test_chunk.rb +4 -4
- data/{tests → test}/test_collection.rb +42 -4
- data/{tests/test_mongo.rb → test/test_connection.rb} +35 -11
- data/test/test_cursor.rb +223 -0
- data/{tests → test}/test_db.rb +12 -12
- data/{tests → test}/test_db_api.rb +28 -33
- data/{tests → test}/test_db_connection.rb +3 -3
- data/{tests → test}/test_grid_store.rb +4 -4
- data/{tests → test}/test_message.rb +1 -1
- data/{tests → test}/test_objectid.rb +3 -3
- data/{tests → test}/test_ordered_hash.rb +0 -0
- data/{tests → test}/test_round_trip.rb +6 -2
- data/{tests → test}/test_threading.rb +3 -3
- data/test/test_xgen.rb +73 -0
- metadata +33 -32
- data/lib/mongo/mongo.rb +0 -164
- data/tests/test_cursor.rb +0 -121
data/lib/mongo/db.rb
CHANGED
@@ -23,549 +23,538 @@ require 'mongo/query'
|
|
23
23
|
require 'mongo/util/ordered_hash.rb'
|
24
24
|
require 'mongo/admin'
|
25
25
|
|
26
|
-
module
|
27
|
-
module Mongo
|
28
|
-
module Driver
|
29
|
-
|
30
|
-
# A Mongo database.
|
31
|
-
class DB
|
32
|
-
|
33
|
-
SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
|
34
|
-
SYSTEM_INDEX_COLLECTION = "system.indexes"
|
35
|
-
SYSTEM_PROFILE_COLLECTION = "system.profile"
|
36
|
-
SYSTEM_USER_COLLECTION = "system.users"
|
37
|
-
SYSTEM_COMMAND_COLLECTION = "$cmd"
|
38
|
-
|
39
|
-
# Strict mode enforces collection existence checks. When +true+,
|
40
|
-
# asking for a collection that does not exist or trying to create a
|
41
|
-
# collection that already exists raises an error.
|
42
|
-
#
|
43
|
-
# Strict mode is off (+false+) by default. Its value can be changed at
|
44
|
-
# any time.
|
45
|
-
attr_writer :strict
|
46
|
-
|
47
|
-
# Returns the value of the +strict+ flag.
|
48
|
-
def strict?; @strict; end
|
49
|
-
|
50
|
-
# The name of the database.
|
51
|
-
attr_reader :name
|
52
|
-
|
53
|
-
# Host to which we are currently connected.
|
54
|
-
attr_reader :host
|
55
|
-
# Port to which we are currently connected.
|
56
|
-
attr_reader :port
|
57
|
-
|
58
|
-
# An array of [host, port] pairs.
|
59
|
-
attr_reader :nodes
|
60
|
-
|
61
|
-
# The database's socket. For internal (and Cursor) use only.
|
62
|
-
attr_reader :socket
|
63
|
-
|
64
|
-
def slave_ok?; @slave_ok; end
|
65
|
-
def auto_reconnect?; @auto_reconnect; end
|
66
|
-
|
67
|
-
# A primary key factory object (or +nil+). See the README.doc file or
|
68
|
-
# DB#new for details.
|
69
|
-
attr_reader :pk_factory
|
70
|
-
|
71
|
-
def pk_factory=(pk_factory)
|
72
|
-
raise "error: can not change PK factory" if @pk_factory
|
73
|
-
@pk_factory = pk_factory
|
74
|
-
end
|
26
|
+
module Mongo
|
75
27
|
|
76
|
-
|
77
|
-
|
78
|
-
# db_name :: The database name
|
79
|
-
#
|
80
|
-
# nodes :: An array of [host, port] pairs. See Mongo#new, which offers
|
81
|
-
# a more flexible way of defining nodes.
|
82
|
-
#
|
83
|
-
# options :: A hash of options.
|
84
|
-
#
|
85
|
-
# Options:
|
86
|
-
#
|
87
|
-
# :strict :: If true, collections must exist to be accessed and must
|
88
|
-
# not exist to be created. See #collection and
|
89
|
-
# #create_collection.
|
90
|
-
#
|
91
|
-
# :pk :: A primary key factory object that must respond to :create_pk,
|
92
|
-
# which should take a hash and return a hash which merges the
|
93
|
-
# original hash with any primary key fields the factory wishes
|
94
|
-
# to inject. (NOTE: if the object already has a primary key,
|
95
|
-
# the factory should not inject a new key; this means that the
|
96
|
-
# object is being used in a repsert but it already exists.) The
|
97
|
-
# idea here is that when ever a record is inserted, the :pk
|
98
|
-
# object's +create_pk+ method will be called and the new hash
|
99
|
-
# returned will be inserted.
|
100
|
-
#
|
101
|
-
# :slave_ok :: Only used if +nodes+ contains only one host/port. If
|
102
|
-
# false, when connecting to that host/port we check to
|
103
|
-
# see if the server is the master. If it is not, an error
|
104
|
-
# is thrown.
|
105
|
-
#
|
106
|
-
# :auto_reconnect :: If the connection gets closed (for example, we
|
107
|
-
# have a server pair and saw the "not master"
|
108
|
-
# error, which closes the connection), then
|
109
|
-
# automatically try to reconnect to the master or
|
110
|
-
# to the single server we have been given. Defaults
|
111
|
-
# to +false+.
|
112
|
-
#
|
113
|
-
# When a DB object first connects to a pair, it will find the master
|
114
|
-
# instance and connect to that one. On socket error or if we recieve a
|
115
|
-
# "not master" error, we again find the master of the pair.
|
116
|
-
def initialize(db_name, nodes, options={})
|
117
|
-
case db_name
|
118
|
-
when Symbol, String
|
119
|
-
else
|
120
|
-
raise TypeError, "db_name must be a string or symbol"
|
121
|
-
end
|
28
|
+
# A Mongo database.
|
29
|
+
class DB
|
122
30
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
if db_name.empty?
|
129
|
-
raise InvalidName, "database name cannot be the empty string"
|
130
|
-
end
|
31
|
+
SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
|
32
|
+
SYSTEM_INDEX_COLLECTION = "system.indexes"
|
33
|
+
SYSTEM_PROFILE_COLLECTION = "system.profile"
|
34
|
+
SYSTEM_USER_COLLECTION = "system.users"
|
35
|
+
SYSTEM_COMMAND_COLLECTION = "$cmd"
|
131
36
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
@socket = nil
|
140
|
-
connect_to_master
|
141
|
-
end
|
37
|
+
# Strict mode enforces collection existence checks. When +true+,
|
38
|
+
# asking for a collection that does not exist or trying to create a
|
39
|
+
# collection that already exists raises an error.
|
40
|
+
#
|
41
|
+
# Strict mode is off (+false+) by default. Its value can be changed at
|
42
|
+
# any time.
|
43
|
+
attr_writer :strict
|
142
44
|
|
143
|
-
|
144
|
-
|
145
|
-
@host = @port = nil
|
146
|
-
@nodes.detect { |hp|
|
147
|
-
@host, @port = *hp
|
148
|
-
begin
|
149
|
-
@socket = TCPSocket.new(@host, @port)
|
150
|
-
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
151
|
-
|
152
|
-
# Check for master. Can't call master? because it uses mutex,
|
153
|
-
# which may already be in use during this call.
|
154
|
-
semaphore_is_locked = @semaphore.locked?
|
155
|
-
@semaphore.unlock if semaphore_is_locked
|
156
|
-
is_master = master?
|
157
|
-
@semaphore.lock if semaphore_is_locked
|
158
|
-
|
159
|
-
break if @slave_ok || is_master
|
160
|
-
rescue SocketError, SystemCallError, IOError => ex
|
161
|
-
close if @socket
|
162
|
-
end
|
163
|
-
@socket
|
164
|
-
}
|
165
|
-
raise "error: failed to connect to any given host:port" unless @socket
|
166
|
-
end
|
45
|
+
# Returns the value of the +strict+ flag.
|
46
|
+
def strict?; @strict; end
|
167
47
|
|
168
|
-
|
169
|
-
|
170
|
-
# plaintext password.
|
171
|
-
def authenticate(username, password)
|
172
|
-
doc = db_command(:getnonce => 1)
|
173
|
-
raise "error retrieving nonce: #{doc}" unless ok?(doc)
|
174
|
-
nonce = doc['nonce']
|
175
|
-
|
176
|
-
auth = OrderedHash.new
|
177
|
-
auth['authenticate'] = 1
|
178
|
-
auth['user'] = username
|
179
|
-
auth['nonce'] = nonce
|
180
|
-
auth['key'] = Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
|
181
|
-
ok?(db_command(auth))
|
182
|
-
end
|
48
|
+
# The name of the database.
|
49
|
+
attr_reader :name
|
183
50
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
end
|
51
|
+
# Host to which we are currently connected.
|
52
|
+
attr_reader :host
|
53
|
+
# Port to which we are currently connected.
|
54
|
+
attr_reader :port
|
189
55
|
|
190
|
-
|
191
|
-
|
192
|
-
names = collections_info.collect { |doc| doc['name'] || '' }
|
193
|
-
names = names.delete_if {|name| name.index(@name).nil? || name.index('$')}
|
194
|
-
names.map {|name| name.sub(@name + '.', '')}
|
195
|
-
end
|
56
|
+
# An array of [host, port] pairs.
|
57
|
+
attr_reader :nodes
|
196
58
|
|
197
|
-
|
198
|
-
|
199
|
-
# specified, an array of length 1 is returned.
|
200
|
-
def collections_info(coll_name=nil)
|
201
|
-
selector = {}
|
202
|
-
selector[:name] = full_coll_name(coll_name) if coll_name
|
203
|
-
query(Collection.new(self, SYSTEM_NAMESPACE_COLLECTION), Query.new(selector))
|
204
|
-
end
|
59
|
+
# The database's socket. For internal (and Cursor) use only.
|
60
|
+
attr_reader :socket
|
205
61
|
|
206
|
-
|
207
|
-
|
208
|
-
# collection +name+ already exists.
|
209
|
-
#
|
210
|
-
# Options is an optional hash:
|
211
|
-
#
|
212
|
-
# :capped :: Boolean. If not specified, capped is +false+.
|
213
|
-
#
|
214
|
-
# :size :: If +capped+ is +true+, specifies the maximum number of
|
215
|
-
# bytes. If +false+, specifies the initial extent of the
|
216
|
-
# collection.
|
217
|
-
#
|
218
|
-
# :max :: Max number of records in a capped collection. Optional.
|
219
|
-
def create_collection(name, options={})
|
220
|
-
# First check existence
|
221
|
-
if collection_names.include?(name)
|
222
|
-
if strict?
|
223
|
-
raise "Collection #{name} already exists. Currently in strict mode."
|
224
|
-
else
|
225
|
-
return Collection.new(self, name)
|
226
|
-
end
|
227
|
-
end
|
62
|
+
def slave_ok?; @slave_ok; end
|
63
|
+
def auto_reconnect?; @auto_reconnect; end
|
228
64
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
doc = db_command(oh.merge(options || {}))
|
233
|
-
ok = doc['ok']
|
234
|
-
return Collection.new(self, name) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
|
235
|
-
raise "Error creating collection: #{doc.inspect}"
|
236
|
-
end
|
65
|
+
# A primary key factory object (or +nil+). See the README.doc file or
|
66
|
+
# DB#new for details.
|
67
|
+
attr_reader :pk_factory
|
237
68
|
|
238
|
-
|
239
|
-
|
240
|
-
|
69
|
+
def pk_factory=(pk_factory)
|
70
|
+
raise "error: can not change PK factory" if @pk_factory
|
71
|
+
@pk_factory = pk_factory
|
72
|
+
end
|
241
73
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
74
|
+
# Instances of DB are normally obtained by calling Mongo#db.
|
75
|
+
#
|
76
|
+
# db_name :: The database name
|
77
|
+
#
|
78
|
+
# nodes :: An array of [host, port] pairs. See Connection#new, which offers
|
79
|
+
# a more flexible way of defining nodes.
|
80
|
+
#
|
81
|
+
# options :: A hash of options.
|
82
|
+
#
|
83
|
+
# Options:
|
84
|
+
#
|
85
|
+
# :strict :: If true, collections must exist to be accessed and must
|
86
|
+
# not exist to be created. See #collection and
|
87
|
+
# #create_collection.
|
88
|
+
#
|
89
|
+
# :pk :: A primary key factory object that must respond to :create_pk,
|
90
|
+
# which should take a hash and return a hash which merges the
|
91
|
+
# original hash with any primary key fields the factory wishes
|
92
|
+
# to inject. (NOTE: if the object already has a primary key,
|
93
|
+
# the factory should not inject a new key; this means that the
|
94
|
+
# object is being used in a repsert but it already exists.) The
|
95
|
+
# idea here is that when ever a record is inserted, the :pk
|
96
|
+
# object's +create_pk+ method will be called and the new hash
|
97
|
+
# returned will be inserted.
|
98
|
+
#
|
99
|
+
# :slave_ok :: Only used if +nodes+ contains only one host/port. If
|
100
|
+
# false, when connecting to that host/port we check to
|
101
|
+
# see if the server is the master. If it is not, an error
|
102
|
+
# is thrown.
|
103
|
+
#
|
104
|
+
# :auto_reconnect :: If the connection gets closed (for example, we
|
105
|
+
# have a server pair and saw the "not master"
|
106
|
+
# error, which closes the connection), then
|
107
|
+
# automatically try to reconnect to the master or
|
108
|
+
# to the single server we have been given. Defaults
|
109
|
+
# to +false+.
|
110
|
+
#
|
111
|
+
# When a DB object first connects to a pair, it will find the master
|
112
|
+
# instance and connect to that one. On socket error or if we recieve a
|
113
|
+
# "not master" error, we again find the master of the pair.
|
114
|
+
def initialize(db_name, nodes, options={})
|
115
|
+
case db_name
|
116
|
+
when Symbol, String
|
117
|
+
else
|
118
|
+
raise TypeError, "db_name must be a string or symbol"
|
119
|
+
end
|
120
|
+
|
121
|
+
[" ", ".", "$", "/", "\\"].each do |invalid_char|
|
122
|
+
if db_name.include? invalid_char
|
123
|
+
raise InvalidName, "database names cannot contain the character '#{invalid_char}'"
|
248
124
|
end
|
249
|
-
|
125
|
+
end
|
126
|
+
if db_name.empty?
|
127
|
+
raise InvalidName, "database name cannot be the empty string"
|
128
|
+
end
|
250
129
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
130
|
+
@name, @nodes = db_name, nodes
|
131
|
+
@strict = options[:strict]
|
132
|
+
@pk_factory = options[:pk]
|
133
|
+
@slave_ok = options[:slave_ok] && @nodes.length == 1 # only OK if one node
|
134
|
+
@auto_reconnect = options[:auto_reconnect]
|
135
|
+
@semaphore = Object.new
|
136
|
+
@semaphore.extend Mutex_m
|
137
|
+
@socket = nil
|
138
|
+
connect_to_master
|
139
|
+
end
|
255
140
|
|
256
|
-
|
141
|
+
def connect_to_master
|
142
|
+
close if @socket
|
143
|
+
@host = @port = nil
|
144
|
+
@nodes.detect { |hp|
|
145
|
+
@host, @port = *hp
|
146
|
+
begin
|
147
|
+
@socket = TCPSocket.new(@host, @port)
|
148
|
+
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
149
|
+
|
150
|
+
# Check for master. Can't call master? because it uses mutex,
|
151
|
+
# which may already be in use during this call.
|
152
|
+
semaphore_is_locked = @semaphore.locked?
|
153
|
+
@semaphore.unlock if semaphore_is_locked
|
154
|
+
is_master = master?
|
155
|
+
@semaphore.lock if semaphore_is_locked
|
156
|
+
|
157
|
+
break if @slave_ok || is_master
|
158
|
+
rescue SocketError, SystemCallError, IOError => ex
|
159
|
+
close if @socket
|
257
160
|
end
|
161
|
+
@socket
|
162
|
+
}
|
163
|
+
raise "error: failed to connect to any given host:port" unless @socket
|
164
|
+
end
|
258
165
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
166
|
+
# Returns true if +username+ has +password+ in
|
167
|
+
# +SYSTEM_USER_COLLECTION+. +name+ is username, +password+ is
|
168
|
+
# plaintext password.
|
169
|
+
def authenticate(username, password)
|
170
|
+
doc = db_command(:getnonce => 1)
|
171
|
+
raise "error retrieving nonce: #{doc}" unless ok?(doc)
|
172
|
+
nonce = doc['nonce']
|
173
|
+
|
174
|
+
auth = OrderedHash.new
|
175
|
+
auth['authenticate'] = 1
|
176
|
+
auth['user'] = username
|
177
|
+
auth['nonce'] = nonce
|
178
|
+
auth['key'] = Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
|
179
|
+
ok?(db_command(auth))
|
180
|
+
end
|
270
181
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
# so that all operations will set the error if needed.
|
277
|
-
def error?
|
278
|
-
error != nil
|
279
|
-
end
|
182
|
+
# Deauthorizes use for this database for this connection.
|
183
|
+
def logout
|
184
|
+
doc = db_command(:logout => 1)
|
185
|
+
raise "error logging out: #{doc.inspect}" unless ok?(doc)
|
186
|
+
end
|
280
187
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
if error["err"]
|
288
|
-
error
|
289
|
-
else
|
290
|
-
nil
|
291
|
-
end
|
292
|
-
end
|
188
|
+
# Returns an array of collection names in this database.
|
189
|
+
def collection_names
|
190
|
+
names = collections_info.collect { |doc| doc['name'] || '' }
|
191
|
+
names = names.delete_if {|name| name.index(@name).nil? || name.index('$')}
|
192
|
+
names.map {|name| name.sub(@name + '.', '')}
|
193
|
+
end
|
293
194
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
195
|
+
# Returns a cursor over query result hashes. Each hash contains a
|
196
|
+
# 'name' string and optionally an 'options' hash. If +coll_name+ is
|
197
|
+
# specified, an array of length 1 is returned.
|
198
|
+
def collections_info(coll_name=nil)
|
199
|
+
selector = {}
|
200
|
+
selector[:name] = full_coll_name(coll_name) if coll_name
|
201
|
+
query(Collection.new(self, SYSTEM_NAMESPACE_COLLECTION), Query.new(selector))
|
202
|
+
end
|
301
203
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
204
|
+
# Create a collection. If +strict+ is false, will return existing or
|
205
|
+
# new collection. If +strict+ is true, will raise an error if
|
206
|
+
# collection +name+ already exists.
|
207
|
+
#
|
208
|
+
# Options is an optional hash:
|
209
|
+
#
|
210
|
+
# :capped :: Boolean. If not specified, capped is +false+.
|
211
|
+
#
|
212
|
+
# :size :: If +capped+ is +true+, specifies the maximum number of
|
213
|
+
# bytes. If +false+, specifies the initial extent of the
|
214
|
+
# collection.
|
215
|
+
#
|
216
|
+
# :max :: Max number of records in a capped collection. Optional.
|
217
|
+
def create_collection(name, options={})
|
218
|
+
# First check existence
|
219
|
+
if collection_names.include?(name)
|
220
|
+
if strict?
|
221
|
+
raise "Collection #{name} already exists. Currently in strict mode."
|
222
|
+
else
|
223
|
+
return Collection.new(self, name)
|
308
224
|
end
|
225
|
+
end
|
309
226
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
"#@host:#@port"
|
319
|
-
else
|
320
|
-
doc['remote']
|
321
|
-
end
|
322
|
-
end
|
227
|
+
# Create new collection
|
228
|
+
oh = OrderedHash.new
|
229
|
+
oh[:create] = name
|
230
|
+
doc = db_command(oh.merge(options || {}))
|
231
|
+
ok = doc['ok']
|
232
|
+
return Collection.new(self, name) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
|
233
|
+
raise "Error creating collection: #{doc.inspect}"
|
234
|
+
end
|
323
235
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
s = @socket
|
328
|
-
@socket = nil
|
329
|
-
s.close
|
330
|
-
end
|
331
|
-
end
|
236
|
+
def admin
|
237
|
+
Admin.new(self)
|
238
|
+
end
|
332
239
|
|
333
|
-
|
334
|
-
|
335
|
-
|
240
|
+
# Return a collection. If +strict+ is false, will return existing or
|
241
|
+
# new collection. If +strict+ is true, will raise an error if
|
242
|
+
# collection +name+ does not already exists.
|
243
|
+
def collection(name)
|
244
|
+
return Collection.new(self, name) if !strict? || collection_names.include?(name)
|
245
|
+
raise "Collection #{name} doesn't exist. Currently in strict mode."
|
246
|
+
end
|
247
|
+
alias_method :[], :collection
|
336
248
|
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
raise "connection closed" unless chunk.length > 0
|
342
|
-
message += chunk
|
343
|
-
end
|
344
|
-
message
|
345
|
-
end
|
249
|
+
# Drop collection +name+. Returns +true+ on success or if the
|
250
|
+
# collection does not exist, +false+ otherwise.
|
251
|
+
def drop_collection(name)
|
252
|
+
return true unless collection_names.include?(name)
|
346
253
|
|
347
|
-
|
348
|
-
|
349
|
-
send_to_db(MsgMessage.new(msg))
|
350
|
-
end
|
254
|
+
ok?(db_command(:drop => name))
|
255
|
+
end
|
351
256
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
257
|
+
# Returns the error message from the most recently executed database
|
258
|
+
# operation for this connection, or +nil+ if there was no error.
|
259
|
+
#
|
260
|
+
# Note: as of this writing, errors are only detected on the db server
|
261
|
+
# for certain kinds of operations (writes). The plan is to change this
|
262
|
+
# so that all operations will set the error if needed.
|
263
|
+
def error
|
264
|
+
doc = db_command(:getlasterror => 1)
|
265
|
+
raise "error retrieving last error: #{doc}" unless ok?(doc)
|
266
|
+
doc['err']
|
267
|
+
end
|
360
268
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
269
|
+
# Returns +true+ if an error was caused by the most recently executed
|
270
|
+
# database operation.
|
271
|
+
#
|
272
|
+
# Note: as of this writing, errors are only detected on the db server
|
273
|
+
# for certain kinds of operations (writes). The plan is to change this
|
274
|
+
# so that all operations will set the error if needed.
|
275
|
+
def error?
|
276
|
+
error != nil
|
277
|
+
end
|
365
278
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
279
|
+
# Get the most recent error to have occured on this database
|
280
|
+
#
|
281
|
+
# Only returns errors that have occured since the last call to
|
282
|
+
# DB#reset_error_history - returns +nil+ if there is no such error.
|
283
|
+
def previous_error
|
284
|
+
error = db_command(:getpreverror => 1)
|
285
|
+
if error["err"]
|
286
|
+
error
|
287
|
+
else
|
288
|
+
nil
|
289
|
+
end
|
290
|
+
end
|
373
291
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
292
|
+
# Reset the error history of this database
|
293
|
+
#
|
294
|
+
# Calls to DB#previous_error will only return errors that have occurred
|
295
|
+
# since the most recent call to this method.
|
296
|
+
def reset_error_history
|
297
|
+
db_command(:reseterror => 1)
|
298
|
+
end
|
381
299
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
300
|
+
# Returns true if this database is a master (or is not paired with any
|
301
|
+
# other database), false if it is a slave.
|
302
|
+
def master?
|
303
|
+
doc = db_command(:ismaster => 1)
|
304
|
+
is_master = doc['ismaster']
|
305
|
+
ok?(doc) && is_master.kind_of?(Numeric) && is_master.to_i == 1
|
306
|
+
end
|
387
307
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
308
|
+
# Returns a string of the form "host:port" that points to the master
|
309
|
+
# database. Works even if this is the master database.
|
310
|
+
def master
|
311
|
+
doc = db_command(:ismaster => 1)
|
312
|
+
is_master = doc['ismaster']
|
313
|
+
raise "Error retrieving master database: #{doc.inspect}" unless ok?(doc) && is_master.kind_of?(Numeric)
|
314
|
+
case is_master.to_i
|
315
|
+
when 1
|
316
|
+
"#@host:#@port"
|
317
|
+
else
|
318
|
+
doc['remote']
|
319
|
+
end
|
320
|
+
end
|
398
321
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
return doc['n'].to_i if ok?(doc)
|
408
|
-
return 0 if doc['errmsg'] == "ns missing"
|
409
|
-
raise "Error with count command: #{doc.inspect}"
|
410
|
-
end
|
322
|
+
# Close the connection to the database.
|
323
|
+
def close
|
324
|
+
if @socket
|
325
|
+
s = @socket
|
326
|
+
@socket = nil
|
327
|
+
s.close
|
328
|
+
end
|
329
|
+
end
|
411
330
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
end
|
331
|
+
def connected?
|
332
|
+
@socket != nil
|
333
|
+
end
|
416
334
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
335
|
+
def receive_full(length)
|
336
|
+
message = ""
|
337
|
+
while message.length < length do
|
338
|
+
chunk = @socket.recv(length - message.length)
|
339
|
+
raise "connection closed" unless chunk.length > 0
|
340
|
+
message += chunk
|
341
|
+
end
|
342
|
+
message
|
343
|
+
end
|
425
344
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
return doc['retval'] if ok?(doc)
|
431
|
-
raise "Error with eval command: #{doc.inspect}"
|
432
|
-
end
|
345
|
+
# Send a MsgMessage to the database.
|
346
|
+
def send_message(msg)
|
347
|
+
send_to_db(MsgMessage.new(msg))
|
348
|
+
end
|
433
349
|
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
end
|
350
|
+
# Returns a Cursor over the query results.
|
351
|
+
#
|
352
|
+
# Note that the query gets sent lazily; the cursor calls
|
353
|
+
# #send_query_message when needed. If the caller never requests an
|
354
|
+
# object from the cursor, the query never gets sent.
|
355
|
+
def query(collection, query, admin=false)
|
356
|
+
Cursor.new(self, collection, query, admin)
|
357
|
+
end
|
443
358
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
oh[:deleteIndexes] = collection_name
|
449
|
-
oh[:index] = name
|
450
|
-
doc = db_command(oh)
|
451
|
-
raise "Error with drop_index command: #{doc.inspect}" unless ok?(doc)
|
452
|
-
end
|
359
|
+
# Used by a Cursor to lazily send the query to the database.
|
360
|
+
def send_query_message(query_message)
|
361
|
+
send_to_db(query_message)
|
362
|
+
end
|
453
363
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
info = {}
|
462
|
-
query(Collection.new(self, SYSTEM_INDEX_COLLECTION), Query.new(sel)).each { |index|
|
463
|
-
info[index['name']] = index['key'].to_a
|
464
|
-
}
|
465
|
-
info
|
466
|
-
end
|
364
|
+
# Remove the records that match +selector+ from +collection_name+.
|
365
|
+
# Normally called by Collection#remove or Collection#clear.
|
366
|
+
def remove_from_db(collection_name, selector)
|
367
|
+
_synchronize {
|
368
|
+
send_to_db(RemoveMessage.new(@name, collection_name, selector))
|
369
|
+
}
|
370
|
+
end
|
467
371
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
field_h = OrderedHash.new
|
476
|
-
if field_or_spec.is_a?(String) || field_or_spec.is_a?(Symbol)
|
477
|
-
field_h[field_or_spec.to_s] = 1
|
478
|
-
else
|
479
|
-
field_or_spec.each { |f| field_h[f[0].to_s] = f[1] }
|
480
|
-
end
|
481
|
-
name = gen_index_name(field_h)
|
482
|
-
sel = {
|
483
|
-
:name => name,
|
484
|
-
:ns => full_coll_name(collection_name),
|
485
|
-
:key => field_h,
|
486
|
-
:unique => unique
|
487
|
-
}
|
488
|
-
_synchronize {
|
489
|
-
send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, false, sel))
|
490
|
-
}
|
491
|
-
name
|
492
|
-
end
|
372
|
+
# Update records in +collection_name+ that match +selector+ by
|
373
|
+
# applying +obj+ as an update. Normally called by Collection#replace.
|
374
|
+
def replace_in_db(collection_name, selector, obj)
|
375
|
+
_synchronize {
|
376
|
+
send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, false))
|
377
|
+
}
|
378
|
+
end
|
493
379
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
if @pk_factory
|
500
|
-
objects.collect! { |o|
|
501
|
-
@pk_factory.create_pk(o)
|
502
|
-
}
|
503
|
-
else
|
504
|
-
objects = objects.collect do |o|
|
505
|
-
o[:_id] || o['_id'] ? o : o.merge!(:_id => ObjectID.new)
|
506
|
-
end
|
507
|
-
end
|
508
|
-
send_to_db(InsertMessage.new(@name, collection_name, true, *objects))
|
509
|
-
objects.collect { |o| o[:_id] || o['_id'] }
|
510
|
-
}
|
511
|
-
end
|
380
|
+
# DEPRECATED - use Collection#update instead
|
381
|
+
def modify_in_db(collection_name, selector, obj)
|
382
|
+
warn "DB#modify_in_db is deprecated and will be removed. Please use Collection#update instead."
|
383
|
+
replace_in_db(collection_name, selector, obj)
|
384
|
+
end
|
512
385
|
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
386
|
+
# Update records in +collection_name+ that match +selector+ by
|
387
|
+
# applying +obj+ as an update. If no match, inserts (???). Normally
|
388
|
+
# called by Collection#repsert.
|
389
|
+
def repsert_in_db(collection_name, selector, obj)
|
390
|
+
_synchronize {
|
391
|
+
obj = @pk_factory.create_pk(obj) if @pk_factory
|
392
|
+
send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, true))
|
393
|
+
obj
|
394
|
+
}
|
395
|
+
end
|
523
396
|
|
524
|
-
|
525
|
-
|
526
|
-
|
397
|
+
# DEPRECATED - use Collection.find(selector).count() instead
|
398
|
+
def count(collection_name, selector={})
|
399
|
+
warn "DB#count is deprecated and will be removed. Please use Collection.find(selector).count instead."
|
400
|
+
collection(collection_name).find(selector).count()
|
401
|
+
end
|
527
402
|
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
end
|
403
|
+
# Dereference a DBRef, getting the document it points to.
|
404
|
+
def dereference(dbref)
|
405
|
+
collection(dbref.namespace).find_one("_id" => dbref.object_id)
|
406
|
+
end
|
533
407
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
raise "db_command must be given an OrderedHash when there is more than one key"
|
543
|
-
end
|
544
|
-
end
|
408
|
+
# Evaluate a JavaScript expression on MongoDB.
|
409
|
+
# +code+ should be a string or Code instance containing a JavaScript
|
410
|
+
# expression. Additional arguments will be passed to that expression
|
411
|
+
# when it is run on the server.
|
412
|
+
def eval(code, *args)
|
413
|
+
if not code.is_a? Code
|
414
|
+
code = Code.new(code)
|
415
|
+
end
|
545
416
|
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
417
|
+
oh = OrderedHash.new
|
418
|
+
oh[:$eval] = code
|
419
|
+
oh[:args] = args
|
420
|
+
doc = db_command(oh)
|
421
|
+
return doc['retval'] if ok?(doc)
|
422
|
+
raise OperationFailure, "eval failed: #{doc['errmsg']}"
|
423
|
+
end
|
550
424
|
|
551
|
-
|
552
|
-
|
553
|
-
|
425
|
+
# Rename collection +from+ to +to+. Meant to be called by
|
426
|
+
# Collection#rename.
|
427
|
+
def rename_collection(from, to)
|
428
|
+
oh = OrderedHash.new
|
429
|
+
oh[:renameCollection] = "#{@name}.#{from}"
|
430
|
+
oh[:to] = "#{@name}.#{to}"
|
431
|
+
doc = db_command(oh, true)
|
432
|
+
raise "Error renaming collection: #{doc.inspect}" unless ok?(doc)
|
433
|
+
end
|
554
434
|
|
555
|
-
|
435
|
+
# Drop index +name+ from +collection_name+. Normally called from
|
436
|
+
# Collection#drop_index or Collection#drop_indexes.
|
437
|
+
def drop_index(collection_name, name)
|
438
|
+
oh = OrderedHash.new
|
439
|
+
oh[:deleteIndexes] = collection_name
|
440
|
+
oh[:index] = name
|
441
|
+
doc = db_command(oh)
|
442
|
+
raise "Error with drop_index command: #{doc.inspect}" unless ok?(doc)
|
443
|
+
end
|
556
444
|
|
557
|
-
|
558
|
-
|
559
|
-
|
445
|
+
# Get information on the indexes for the collection +collection_name+.
|
446
|
+
# Normally called by Collection#index_information. Returns a hash where
|
447
|
+
# the keys are index names (as returned by Collection#create_index and
|
448
|
+
# the values are lists of [key, direction] pairs specifying the index
|
449
|
+
# (as passed to Collection#create_index).
|
450
|
+
def index_information(collection_name)
|
451
|
+
sel = {:ns => full_coll_name(collection_name)}
|
452
|
+
info = {}
|
453
|
+
query(Collection.new(self, SYSTEM_INDEX_COLLECTION), Query.new(sel)).each { |index|
|
454
|
+
info[index['name']] = index['key'].to_a
|
455
|
+
}
|
456
|
+
info
|
457
|
+
end
|
458
|
+
|
459
|
+
# Create a new index on +collection_name+. +field_or_spec+
|
460
|
+
# should be either a single field name or a Array of [field name,
|
461
|
+
# direction] pairs. Directions should be specified as
|
462
|
+
# Mongo::ASCENDING or Mongo::DESCENDING. Normally called
|
463
|
+
# by Collection#create_index. If +unique+ is true the index will
|
464
|
+
# enforce a uniqueness constraint.
|
465
|
+
def create_index(collection_name, field_or_spec, unique=false)
|
466
|
+
field_h = OrderedHash.new
|
467
|
+
if field_or_spec.is_a?(String) || field_or_spec.is_a?(Symbol)
|
468
|
+
field_h[field_or_spec.to_s] = 1
|
469
|
+
else
|
470
|
+
field_or_spec.each { |f| field_h[f[0].to_s] = f[1] }
|
471
|
+
end
|
472
|
+
name = gen_index_name(field_h)
|
473
|
+
sel = {
|
474
|
+
:name => name,
|
475
|
+
:ns => full_coll_name(collection_name),
|
476
|
+
:key => field_h,
|
477
|
+
:unique => unique
|
478
|
+
}
|
479
|
+
_synchronize {
|
480
|
+
send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, false, sel))
|
481
|
+
}
|
482
|
+
name
|
483
|
+
end
|
560
484
|
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
485
|
+
# Insert +objects+ into +collection_name+. Normally called by
|
486
|
+
# Collection#insert. Returns a new array containing the _ids
|
487
|
+
# of the inserted documents.
|
488
|
+
def insert_into_db(collection_name, objects)
|
489
|
+
_synchronize {
|
490
|
+
if @pk_factory
|
491
|
+
objects.collect! { |o|
|
492
|
+
@pk_factory.create_pk(o)
|
565
493
|
}
|
566
|
-
|
494
|
+
else
|
495
|
+
objects = objects.collect do |o|
|
496
|
+
o[:_id] || o['_id'] ? o : o.merge!(:_id => ObjectID.new)
|
497
|
+
end
|
567
498
|
end
|
499
|
+
send_to_db(InsertMessage.new(@name, collection_name, true, *objects))
|
500
|
+
objects.collect { |o| o[:_id] || o['_id'] }
|
501
|
+
}
|
502
|
+
end
|
503
|
+
|
504
|
+
def send_to_db(message)
|
505
|
+
connect_to_master if !connected? && @auto_reconnect
|
506
|
+
begin
|
507
|
+
@socket.print(message.buf.to_s)
|
508
|
+
@socket.flush
|
509
|
+
rescue => ex
|
510
|
+
close
|
511
|
+
raise ex
|
568
512
|
end
|
569
513
|
end
|
514
|
+
|
515
|
+
def full_coll_name(collection_name)
|
516
|
+
"#{@name}.#{collection_name}"
|
517
|
+
end
|
518
|
+
|
519
|
+
# Return +true+ if +doc+ contains an 'ok' field with the value 1.
|
520
|
+
def ok?(doc)
|
521
|
+
ok = doc['ok']
|
522
|
+
ok.kind_of?(Numeric) && ok.to_i == 1
|
523
|
+
end
|
524
|
+
|
525
|
+
# DB commands need to be ordered, so selector must be an OrderedHash
|
526
|
+
# (or a Hash with only one element). What DB commands really need is
|
527
|
+
# that the "command" key be first.
|
528
|
+
#
|
529
|
+
# Do not call this. Intended for driver use only.
|
530
|
+
def db_command(selector, use_admin_db=false)
|
531
|
+
if !selector.kind_of?(OrderedHash)
|
532
|
+
if !selector.kind_of?(Hash) || selector.keys.length > 1
|
533
|
+
raise "db_command must be given an OrderedHash when there is more than one key"
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
q = Query.new(selector)
|
538
|
+
q.number_to_return = 1
|
539
|
+
query(Collection.new(self, SYSTEM_COMMAND_COLLECTION), q, use_admin_db).next_object
|
540
|
+
end
|
541
|
+
|
542
|
+
def _synchronize &block
|
543
|
+
@semaphore.synchronize &block
|
544
|
+
end
|
545
|
+
|
546
|
+
private
|
547
|
+
|
548
|
+
def hash_password(username, plaintext)
|
549
|
+
Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
|
550
|
+
end
|
551
|
+
|
552
|
+
def gen_index_name(spec)
|
553
|
+
temp = []
|
554
|
+
spec.each_pair { |field, direction|
|
555
|
+
temp = temp.push("#{field}_#{direction}")
|
556
|
+
}
|
557
|
+
return temp.join("_")
|
558
|
+
end
|
570
559
|
end
|
571
560
|
end
|