em-mongo 0.3.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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