em-mongo 0.3.6 → 0.4.0

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.
@@ -8,51 +8,379 @@ module EM::Mongo
8
8
  SYSTEM_JS_COLLECTION = "system.js"
9
9
  SYSTEM_COMMAND_COLLECTION = "$cmd"
10
10
 
11
+ # The length of time that Collection.ensure_index should cache index calls
12
+ attr_accessor :cache_time
13
+
14
+ # @param [String] name the database name.
15
+ # @param [EM::Mongo::Connection] connection a connection object pointing to MongoDB. Note
16
+ # that databases are usually instantiated via the Connection class. See the examples below.
17
+ #
18
+ # @core databases constructor_details
11
19
  def initialize(name = DEFAULT_DB, connection = nil)
12
20
  @db_name = name
13
21
  @em_connection = connection || EM::Mongo::Connection.new
14
22
  @collection = nil
15
23
  @collections = {}
24
+ @cache_time = 300 #5 minutes.
16
25
  end
17
26
 
27
+ # Get a collection by name.
28
+ #
29
+ # @param [String, Symbol] name the collection name.
30
+ #
31
+ # @return [EM::Mongo::Collection]
18
32
  def collection(name = EM::Mongo::DEFAULT_NS)
19
33
  @collections[name] ||= EM::Mongo::Collection.new(@db_name, name, @em_connection)
20
34
  end
21
35
 
36
+ # Get the connection associated with this database
37
+ #
38
+ # @return [EM::Mongo::Connection]
22
39
  def connection
23
40
  @em_connection
24
41
  end
25
42
 
26
- def close
27
- @em_connection.close
43
+ #Get the name of this database
44
+ #
45
+ # @return [String]
46
+ def name
47
+ @db_name
28
48
  end
29
49
 
30
- def authenticate(username, password)
31
- self.collection(SYSTEM_COMMAND_COLLECTION).first({'getnonce' => 1}) do |res|
32
- yield false if not res or not res['nonce']
33
-
34
- auth = BSON::OrderedHash.new
35
- auth['authenticate'] = 1
36
- auth['user'] = username
37
- auth['nonce'] = res['nonce']
38
- auth['key'] = Mongo::Support.auth_key(username, password, res['nonce'])
39
-
40
- self.collection(SYSTEM_COMMAND_COLLECTION).first(auth) do |res|
41
- if Mongo::Support.ok?(res)
42
- yield true
50
+ # Get an array of collection names in this database.
51
+ #
52
+ # @return [EM::Mongo::RequestResponse]
53
+ def collection_names
54
+ response = RequestResponse.new
55
+ name_resp = collections_info.to_a
56
+ name_resp.callback do |docs|
57
+ names = docs.collect{ |doc| doc['name'] || '' }
58
+ names = names.delete_if {|name| name.index(self.name).nil? || name.index('$')}
59
+ names = names.map{ |name| name.sub(self.name + '.','')}
60
+ response.succeed(names)
61
+ end
62
+ name_resp.errback { |err| response.fail err }
63
+ response
64
+ end
65
+
66
+ # Get an array of Collection instances, one for each collection in this database.
67
+ #
68
+ # @return [EM::Mongo::RequestResponse]
69
+ def collections
70
+ response = RequestResponse.new
71
+ name_resp = collection_names
72
+ name_resp.callback do |names|
73
+ response.succeed names.map do |name|
74
+ EM::Mongo::Collection.new(@db_name, name, @em_connection)
75
+ end
76
+ end
77
+ name_resp.errback { |err| response.fail err }
78
+ response
79
+ end
80
+
81
+ # Get info on system namespaces (collections). This method returns
82
+ # a cursor which can be iterated over. For each collection, a hash
83
+ # will be yielded containing a 'name' string and, optionally, an 'options' hash.
84
+ #
85
+ # @param [String] coll_name return info for the specifed collection only.
86
+ #
87
+ # @return [EM::Mongo::Cursor]
88
+ def collections_info(coll_name=nil)
89
+ selector = {}
90
+ selector[:name] = full_collection_name(coll_name) if coll_name
91
+ Cursor.new(EM::Mongo::Collection.new(@db_name, SYSTEM_NAMESPACE_COLLECTION, @em_connection), :selector => selector)
92
+ end
93
+
94
+ # Create a collection.
95
+ #
96
+ # new collection. If +strict+ is true, will raise an error if
97
+ # collection +name+ already exists.
98
+ #
99
+ # @param [String, Symbol] name the name of the new collection.
100
+ #
101
+ # @option opts [Boolean] :capped (False) created a capped collection.
102
+ #
103
+ # @option opts [Integer] :size (Nil) If +capped+ is +true+,
104
+ # specifies the maximum number of bytes for the capped collection.
105
+ # If +false+, specifies the number of bytes allocated
106
+ # for the initial extent of the collection.
107
+ #
108
+ # @option opts [Integer] :max (Nil) If +capped+ is +true+, indicates
109
+ # the maximum number of records in a capped collection.
110
+ #
111
+ # @raise [MongoDBError] raised under two conditions:
112
+ # either we're in +strict+ mode and the collection
113
+ # already exists or collection creation fails on the server.
114
+ #
115
+ # @return [EM::Mongo::RequestResponse] Calls back with the new collection
116
+ def create_collection(name)
117
+ response = RequestResponse.new
118
+ names_resp = collection_names
119
+ names_resp.callback do |names|
120
+ if names.include?(name.to_s)
121
+ response.succeed EM::Mongo::Collection.new(@db_name, name, @em_connection)
122
+ end
123
+
124
+ # Create a new collection.
125
+ oh = BSON::OrderedHash.new
126
+ oh[:create] = name
127
+ cmd_resp = command(oh)
128
+ cmd_resp.callback do |doc|
129
+ if EM::Mongo::Support.ok?(doc)
130
+ response.succeed EM::Mongo::Collection.new(@db_name, name, @em_connection)
43
131
  else
44
- yield res
132
+ response.fail [MongoDBError, "Error creating collection: #{doc.inspect}"]
133
+ end
134
+ end
135
+ cmd_resp.errback { |err| response.fail err }
136
+ end
137
+ names_resp.errback { |err| response.fail err }
138
+ response
139
+ end
140
+
141
+ # Drop a collection by +name+.
142
+ #
143
+ # @param [String, Symbol] name
144
+ #
145
+ # @return [EM::Mongo::RequestResponse] Calls back with +true+ on success or +false+ if the collection name doesn't exist.
146
+ def drop_collection(name)
147
+ response = RequestResponse.new
148
+ names_resp = collection_names
149
+ names_resp.callback do |names|
150
+ if names.include?(name.to_s)
151
+ cmd_resp = command(:drop=>name)
152
+ cmd_resp.callback do |doc|
153
+ response.succeed EM::Mongo::Support.ok?(doc)
154
+ end
155
+ cmd_resp.errback { |err| response.fail err }
156
+ else
157
+ response.succeed false
158
+ end
159
+ end
160
+ names_resp.errback { |err| response.fail err }
161
+ response
162
+ end
163
+
164
+ # Drop an index from a given collection. Normally called from
165
+ # Collection#drop_index or Collection#drop_indexes.
166
+ #
167
+ # @param [String] collection_name
168
+ # @param [String] index_name
169
+ #
170
+ # @return [EM::Mongo::RequestResponse] returns +true+ on success.
171
+ #
172
+ # @raise MongoDBError if there's an error renaming the collection.
173
+ def drop_index(collection_name, index_name)
174
+ response = RequestResponse.new
175
+ oh = BSON::OrderedHash.new
176
+ oh[:deleteIndexes] = collection_name
177
+ oh[:index] = index_name.to_s
178
+ cmd_resp = command(oh, :check_response => false)
179
+ cmd_resp.callback do |doc|
180
+ if EM::Mongo::Support.ok?(doc)
181
+ response.succeed(true)
182
+ else
183
+ response.fail [MongoDBError, "Error with drop_index command: #{doc.inspect}"]
184
+ end
185
+ end
186
+ cmd_resp.errback do |err|
187
+ response.fail err
188
+ end
189
+ response
190
+ end
191
+
192
+ # Get information on the indexes for the given collection.
193
+ # Normally called by Collection#index_information.
194
+ #
195
+ # @param [String] collection_name
196
+ #
197
+ # @return [EM::Mongo::RequestResponse] Calls back with a hash where keys are index names and the values are lists of [key, direction] pairs
198
+ # defining the index.
199
+ def index_information(collection_name)
200
+ response = RequestResponse.new
201
+ sel = {:ns => full_collection_name(collection_name)}
202
+ idx_resp = Cursor.new(self.collection(SYSTEM_INDEX_COLLECTION), :selector => sel).to_a
203
+ idx_resp.callback do |indexes|
204
+ info = indexes.inject({}) do |info, index|
205
+ info[index['name']] = index
206
+ info
207
+ end
208
+ response.succeed info
209
+ end
210
+ idx_resp.errback do |err|
211
+ fail err
212
+ end
213
+ response
214
+ end
215
+
216
+ # Run the getlasterror command with the specified replication options.
217
+ #
218
+ # @option opts [Boolean] :fsync (false)
219
+ # @option opts [Integer] :w (nil)
220
+ # @option opts [Integer] :wtimeout (nil)
221
+ #
222
+ # @return [EM::Mongo::RequestResponse] the entire response to getlasterror.
223
+ #
224
+ # @raise [MongoDBError] if the operation fails.
225
+ def get_last_error(opts={})
226
+ response = RequestResponse.new
227
+ cmd = BSON::OrderedHash.new
228
+ cmd[:getlasterror] = 1
229
+ cmd.merge!(opts)
230
+ cmd_resp = command(cmd, :check_response => false)
231
+ cmd_resp.callback do |doc|
232
+ if EM::Mongo::Support.ok?(doc)
233
+ response.succeed doc
234
+ else
235
+ response.fail [MongoDBError, "error retrieving last error: #{doc.inspect}"]
236
+ end
237
+ end
238
+ cmd_resp.errback { |err| response.fail err }
239
+ response
240
+ end
241
+
242
+ # Return +true+ if an error was caused by the most recently executed
243
+ # database operation.
244
+ #
245
+ # @return [EM::Mongo::RequestResponse]
246
+ def error?
247
+ response = RequestResponse.new
248
+ err_resp = get_last_error
249
+ err_resp.callback do |doc|
250
+ response.succeed doc['err'] != nil
251
+ end
252
+ err_resp.errback do |err|
253
+ response.fail err
254
+ end
255
+ response
256
+ end
257
+
258
+ # Reset the error history of this database
259
+ #
260
+ # Calls to DB#previous_error will only return errors that have occurred
261
+ # since the most recent call to this method.
262
+ #
263
+ # @return [EM::Mongo::RequestResponse]
264
+ def reset_error_history
265
+ command(:reseterror => 1)
266
+ end
267
+
268
+
269
+ # A shortcut returning db plus dot plus collection name.
270
+ #
271
+ # @param [String] collection_name
272
+ #
273
+ # @return [String]
274
+ def full_collection_name(collection_name)
275
+ "#{name}.#{collection_name}"
276
+ end
277
+
278
+
279
+
280
+ # Send a command to the database.
281
+ #
282
+ # Note: DB commands must start with the "command" key. For this reason,
283
+ # any selector containing more than one key must be an OrderedHash.
284
+ #
285
+ # Note also that a command in MongoDB is just a kind of query
286
+ # that occurs on the system command collection ($cmd). Examine this method's implementation
287
+ # to see how it works.
288
+ #
289
+ # @param [OrderedHash, Hash] selector an OrderedHash, or a standard Hash with just one
290
+ # key, specifying the command to be performed. In Ruby 1.9, OrderedHash isn't necessary since
291
+ # hashes are ordered by default.
292
+ #
293
+ # @option opts [Boolean] :check_response (true) If +true+, raises an exception if the
294
+ # command fails.
295
+ # @option opts [Socket] :socket a socket to use for sending the command. This is mainly for internal use.
296
+ #
297
+ # @return [EM::Mongo::RequestResponse] Calls back with a hash representing the result of the command
298
+ #
299
+ # @core commands command_instance-method
300
+ def command(selector, opts={})
301
+ check_response = opts.fetch(:check_response, true)
302
+ raise MongoArgumentError, "command must be given a selector" unless selector.is_a?(Hash) && !selector.empty?
303
+
304
+ if selector.keys.length > 1 && RUBY_VERSION < '1.9' && selector.class != BSON::OrderedHash
305
+ raise MongoArgumentError, "DB#command requires an OrderedHash when hash contains multiple keys"
306
+ end
307
+
308
+ response = RequestResponse.new
309
+ cmd_resp = Cursor.new(self.collection(SYSTEM_COMMAND_COLLECTION), :limit => -1, :selector => selector).next_document
310
+
311
+ cmd_resp.callback do |doc|
312
+ if doc.nil?
313
+ response.fail([OperationFailure, "Database command '#{selector.keys.first}' failed: returned null."])
314
+ elsif (check_response && !EM::Mongo::Support.ok?(doc))
315
+ response.fail([OperationFailure, "Database command '#{selector.keys.first}' failed: #{doc.inspect}"])
316
+ else
317
+ response.succeed(doc)
318
+ end
319
+ end
320
+
321
+ cmd_resp.errback do |err|
322
+ response.fail([OperationFailure, "Database command '#{selector.keys.first}' failed: #{err[1]}"])
323
+ end
324
+
325
+ response
326
+ end
327
+
328
+ # Authenticate with the given username and password. Note that mongod
329
+ # must be started with the --auth option for authentication to be enabled.
330
+ #
331
+ # @param [String] username
332
+ # @param [String] password
333
+ #
334
+ # @return [EM::Mongo::RequestResponse] Calls back with +true+ or +false+, indicating success or failure
335
+ #
336
+ # @raise [AuthenticationError]
337
+ #
338
+ # @core authenticate authenticate-instance_method
339
+ def authenticate(username, password)
340
+ response = RequestResponse.new
341
+ auth_resp = self.collection(SYSTEM_COMMAND_COLLECTION).first({'getnonce' => 1})
342
+ auth_resp.callback do |res|
343
+ if not res or not res['nonce']
344
+ response.succeed false
345
+ else
346
+ auth = BSON::OrderedHash.new
347
+ auth['authenticate'] = 1
348
+ auth['user'] = username
349
+ auth['nonce'] = res['nonce']
350
+ auth['key'] = EM::Mongo::Support.auth_key(username, password, res['nonce'])
351
+
352
+ auth_resp2 = self.collection(SYSTEM_COMMAND_COLLECTION).first(auth)
353
+ auth_resp2.callback do |res|
354
+ if EM::Mongo::Support.ok?(res)
355
+ response.succeed true
356
+ else
357
+ response.fail res
358
+ end
45
359
  end
360
+ auth_resp2.errback { |err| response.fail err }
46
361
  end
47
362
  end
363
+ auth_resp.errback { |err| response.fail err }
364
+ response
48
365
  end
49
366
 
50
- def add_user(username, password, &blk)
51
- self.collection(SYSTEM_USER_COLLECTION).first({:user => username}) do |res|
367
+ # Adds a user to this database for use with authentication. If the user already
368
+ # exists in the system, the password will be updated.
369
+ #
370
+ # @param [String] username
371
+ # @param [String] password
372
+ #
373
+ # @return [EM::Mongo::RequestResponse] Calls back with an object representing the user.
374
+ def add_user(username, password)
375
+ response = RequestResponse.new
376
+ user_resp = self.collection(SYSTEM_USER_COLLECTION).first({:user => username})
377
+ user_resp.callback do |res|
52
378
  user = res || {:user => username}
53
- user['pwd'] = Mongo::Support.hash_password(username, password)
54
- yield self.collection(SYSTEM_USER_COLLECTION).save(user)
379
+ user['pwd'] = EM::Mongo::Support.hash_password(username, password)
380
+ response.succeed self.collection(SYSTEM_USER_COLLECTION).save(user)
55
381
  end
382
+ user_resp.errback { |err| response.fail err }
383
+ response
56
384
  end
57
385
 
58
386
  end
@@ -1,4 +1,4 @@
1
-
1
+ require "timeout"
2
2
  # encoding: UTF-8
3
3
 
4
4
  #
@@ -18,7 +18,7 @@
18
18
  # limitations under the License.
19
19
  # ++
20
20
 
21
- module Mongo
21
+ module EM::Mongo
22
22
  # Generic Mongo Ruby Driver exception class.
23
23
  class MongoRubyError < StandardError; end
24
24
 
@@ -0,0 +1,53 @@
1
+ module EM
2
+ module Mongo
3
+ class Collection
4
+
5
+ alias :new_find :find
6
+ def find(selector={}, opts={}, &blk)
7
+ raise "find requires a block" if not block_given?
8
+
9
+ new_find(selector, opts).to_a.callback do |docs|
10
+ blk.call(docs)
11
+ end
12
+ end
13
+
14
+ def first(selector={}, opts={}, &blk)
15
+ opts[:limit] = 1
16
+ find(selector, opts) do |res|
17
+ yield res.first
18
+ end
19
+ end
20
+ end
21
+
22
+ class Connection
23
+
24
+ def insert(collection_name, documents)
25
+ db_name, col_name = db_and_col_name(collection_name)
26
+ db(db_name).collection(col_name).insert(documents)
27
+ end
28
+
29
+ def update(collection_name, selector, document, options={})
30
+ db_name, col_name = db_and_col_name(collection_name)
31
+ db(db_name).collection(col_name).update(selector, document, options)
32
+ end
33
+
34
+ def delete(collection_name, selector)
35
+ db_name, col_name = db_and_col_name(collection_name)
36
+ db(db_name).collection(col_name).remove(selector)
37
+ end
38
+
39
+ def find(collection_name, skip, limit, order, query, fields, &blk)
40
+ db_name, col_name = db_and_col_name(collection_name)
41
+ db(db_name).collection(col_name).find(query, :skip=>skip,:limit=>limit,:order=>order,:fields=>fields).to_a.callback do |docs|
42
+ yield docs if block_given?
43
+ end
44
+ end
45
+
46
+ def db_and_col_name(full_name)
47
+ parts = full_name.split(".")
48
+ [ parts.shift, parts.join(".") ]
49
+ end
50
+
51
+ end
52
+ end
53
+ end