mongo-find_replace 0.18.3

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.
Files changed (68) hide show
  1. data/LICENSE.txt +202 -0
  2. data/README.rdoc +358 -0
  3. data/Rakefile +133 -0
  4. data/bin/bson_benchmark.rb +59 -0
  5. data/bin/fail_if_no_c.rb +11 -0
  6. data/examples/admin.rb +42 -0
  7. data/examples/capped.rb +22 -0
  8. data/examples/cursor.rb +48 -0
  9. data/examples/gridfs.rb +88 -0
  10. data/examples/index_test.rb +126 -0
  11. data/examples/info.rb +31 -0
  12. data/examples/queries.rb +70 -0
  13. data/examples/simple.rb +24 -0
  14. data/examples/strict.rb +35 -0
  15. data/examples/types.rb +36 -0
  16. data/lib/mongo.rb +61 -0
  17. data/lib/mongo/admin.rb +95 -0
  18. data/lib/mongo/collection.rb +664 -0
  19. data/lib/mongo/connection.rb +555 -0
  20. data/lib/mongo/cursor.rb +393 -0
  21. data/lib/mongo/db.rb +527 -0
  22. data/lib/mongo/exceptions.rb +60 -0
  23. data/lib/mongo/gridfs.rb +22 -0
  24. data/lib/mongo/gridfs/chunk.rb +90 -0
  25. data/lib/mongo/gridfs/grid_store.rb +555 -0
  26. data/lib/mongo/types/binary.rb +48 -0
  27. data/lib/mongo/types/code.rb +36 -0
  28. data/lib/mongo/types/dbref.rb +38 -0
  29. data/lib/mongo/types/min_max_keys.rb +58 -0
  30. data/lib/mongo/types/objectid.rb +219 -0
  31. data/lib/mongo/types/regexp_of_holding.rb +45 -0
  32. data/lib/mongo/util/bson_c.rb +18 -0
  33. data/lib/mongo/util/bson_ruby.rb +595 -0
  34. data/lib/mongo/util/byte_buffer.rb +222 -0
  35. data/lib/mongo/util/conversions.rb +97 -0
  36. data/lib/mongo/util/ordered_hash.rb +135 -0
  37. data/lib/mongo/util/server_version.rb +69 -0
  38. data/lib/mongo/util/support.rb +26 -0
  39. data/lib/mongo/util/xml_to_ruby.rb +112 -0
  40. data/mongo-ruby-driver.gemspec +28 -0
  41. data/test/replica/count_test.rb +34 -0
  42. data/test/replica/insert_test.rb +50 -0
  43. data/test/replica/pooled_insert_test.rb +54 -0
  44. data/test/replica/query_test.rb +39 -0
  45. data/test/test_admin.rb +67 -0
  46. data/test/test_bson.rb +397 -0
  47. data/test/test_byte_buffer.rb +81 -0
  48. data/test/test_chunk.rb +82 -0
  49. data/test/test_collection.rb +534 -0
  50. data/test/test_connection.rb +160 -0
  51. data/test/test_conversions.rb +120 -0
  52. data/test/test_cursor.rb +386 -0
  53. data/test/test_db.rb +254 -0
  54. data/test/test_db_api.rb +783 -0
  55. data/test/test_db_connection.rb +16 -0
  56. data/test/test_grid_store.rb +306 -0
  57. data/test/test_helper.rb +42 -0
  58. data/test/test_objectid.rb +156 -0
  59. data/test/test_ordered_hash.rb +168 -0
  60. data/test/test_round_trip.rb +114 -0
  61. data/test/test_slave_connection.rb +36 -0
  62. data/test/test_threading.rb +87 -0
  63. data/test/threading/test_threading_large_pool.rb +90 -0
  64. data/test/unit/collection_test.rb +52 -0
  65. data/test/unit/connection_test.rb +59 -0
  66. data/test/unit/cursor_test.rb +94 -0
  67. data/test/unit/db_test.rb +97 -0
  68. metadata +123 -0
@@ -0,0 +1,393 @@
1
+ # Copyright (C) 2008-2009 10gen Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+
17
+ # A cursor over query results. Returned objects are hashes.
18
+ class Cursor
19
+ include Mongo::Conversions
20
+ include Enumerable
21
+
22
+ attr_reader :collection, :selector, :admin, :fields,
23
+ :order, :hint, :snapshot, :timeout,
24
+ :full_collection_name
25
+
26
+ # Create a new cursor.
27
+ #
28
+ # Note: cursors are created when executing queries using [Collection#find] and other
29
+ # similar methods. Application developers shouldn't have to create cursors manually.
30
+ #
31
+ # @return [Cursor]
32
+ def initialize(collection, options={})
33
+ @db = collection.db
34
+ @collection = collection
35
+ @connection = @db.connection
36
+
37
+ @selector = convert_selector_for_query(options[:selector])
38
+ @fields = convert_fields_for_query(options[:fields])
39
+ @admin = options[:admin] || false
40
+ @skip = options[:skip] || 0
41
+ @limit = options[:limit] || 0
42
+ @order = options[:order]
43
+ @hint = options[:hint]
44
+ @snapshot = options[:snapshot]
45
+ @timeout = options[:timeout] || false
46
+ @explain = options[:explain]
47
+ @socket = options[:socket]
48
+
49
+ @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
50
+ @cache = []
51
+ @closed = false
52
+ @query_run = false
53
+ end
54
+
55
+ # Get the next document specified the cursor options.
56
+ #
57
+ # @return [Hash, Nil] the next document or Nil if no documents remain.
58
+ def next_document
59
+ refill_via_get_more if num_remaining == 0
60
+ doc = @cache.shift
61
+
62
+ if doc && doc['$err']
63
+ err = doc['$err']
64
+
65
+ # If the server has stopped being the master (e.g., it's one of a
66
+ # pair but it has died or something like that) then we close that
67
+ # connection. The next request will re-open on master server.
68
+ if err == "not master"
69
+ raise ConnectionFailure, err
70
+ @connection.close
71
+ end
72
+
73
+ raise OperationFailure, err
74
+ end
75
+
76
+ doc
77
+ end
78
+
79
+ # @deprecated use Cursor#next_document instead.
80
+ def next_object
81
+ warn "Cursor#next_object is deprecated; please use Cursor#next_document instead."
82
+ next_document
83
+ end
84
+
85
+ # Get the size of the result set for this query.
86
+ #
87
+ # @return [Integer] the number of objects in the result set for this query. Does
88
+ # not take limit and skip into account.
89
+ #
90
+ # @raise [OperationFailure] on a database error.
91
+ def count
92
+ command = OrderedHash["count", @collection.name,
93
+ "query", @selector,
94
+ "fields", @fields]
95
+ response = @db.command(command)
96
+ return response['n'].to_i if response['ok'] == 1
97
+ return 0 if response['errmsg'] == "ns missing"
98
+ raise OperationFailure, "Count failed: #{response['errmsg']}"
99
+ end
100
+
101
+ # Sort this cursor's results.
102
+ #
103
+ # This method overrides any sort order specified in the Collection#find
104
+ # method, and only the last sort applied has an effect.
105
+ #
106
+ # @param [Symbol, Array] key_or_list either 1) a key to sort by or 2)
107
+ # an array of [key, direction] pairs to sort by. Direction should
108
+ # be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
109
+ #
110
+ # @raise [InvalidOperation] if this cursor has already been used.
111
+ #
112
+ # @raise [InvalidSortValueError] if the specified order is invalid.
113
+ def sort(key_or_list, direction=nil)
114
+ check_modifiable
115
+
116
+ if !direction.nil?
117
+ order = [[key_or_list, direction]]
118
+ else
119
+ order = key_or_list
120
+ end
121
+
122
+ @order = order
123
+ self
124
+ end
125
+
126
+ # Limit the number of results to be returned by this cursor.
127
+ #
128
+ # This method overrides any limit specified in the Collection#find method,
129
+ # and only the last limit applied has an effect.
130
+ #
131
+ # @return [Integer] the current number_to_return if no parameter is given.
132
+ #
133
+ # @raise [InvalidOperation] if this cursor has already been used.
134
+ def limit(number_to_return=nil)
135
+ return @limit unless number_to_return
136
+ check_modifiable
137
+ raise ArgumentError, "limit requires an integer" unless number_to_return.is_a? Integer
138
+
139
+ @limit = number_to_return
140
+ self
141
+ end
142
+
143
+ # Skips the first +number_to_skip+ results of this cursor.
144
+ # Returns the current number_to_skip if no parameter is given.
145
+ #
146
+ # This method overrides any skip specified in the Collection#find method,
147
+ # and only the last skip applied has an effect.
148
+ #
149
+ # @return [Integer]
150
+ #
151
+ # @raise [InvalidOperation] if this cursor has already been used.
152
+ def skip(number_to_skip=nil)
153
+ return @skip unless number_to_skip
154
+ check_modifiable
155
+ raise ArgumentError, "skip requires an integer" unless number_to_skip.is_a? Integer
156
+
157
+ @skip = number_to_skip
158
+ self
159
+ end
160
+
161
+ # Iterate over each document in this cursor, yielding it to the given
162
+ # block.
163
+ #
164
+ # Iterating over an entire cursor will close it.
165
+ #
166
+ # @yield passes each document to a block for processing.
167
+ #
168
+ # @example if 'comments' represents a collection of comments:
169
+ # comments.find.each do |doc|
170
+ # puts doc['user']
171
+ # end
172
+ def each
173
+ num_returned = 0
174
+ while more? && (@limit <= 0 || num_returned < @limit)
175
+ yield next_document
176
+ num_returned += 1
177
+ end
178
+ end
179
+
180
+ # Receive all the documents from this cursor as an array of hashes.
181
+ #
182
+ # Note: use of this method is discouraged - in most cases, it's much more
183
+ # efficient to retrieve documents as you need them by iterating over the cursor.
184
+ #
185
+ # @return [Array] an array of documents.
186
+ #
187
+ # @raise [InvalidOperation] if this cursor has already been used or if
188
+ # this method has already been called on the cursor.
189
+ def to_a
190
+ raise InvalidOperation, "can't call Cursor#to_a on a used cursor" if @query_run
191
+ rows = []
192
+ num_returned = 0
193
+ while more? && (@limit <= 0 || num_returned < @limit)
194
+ rows << next_document
195
+ num_returned += 1
196
+ end
197
+ rows
198
+ end
199
+
200
+ # Get the explain plan for this cursor.
201
+ #
202
+ # @return [Hash] a document containing the explain plan for this cursor.
203
+ def explain
204
+ c = Cursor.new(@collection, query_options_hash.merge(:limit => -@limit.abs, :explain => true))
205
+ explanation = c.next_document
206
+ c.close
207
+
208
+ explanation
209
+ end
210
+
211
+ # Close the cursor.
212
+ #
213
+ # Note: if a cursor is read until exhausted (read until Mongo::Constants::OP_QUERY or
214
+ # Mongo::Constants::OP_GETMORE returns zero for the cursor id), there is no need to
215
+ # close it manually.
216
+ #
217
+ # Note also: Collection#find takes an optional block argument which can be used to
218
+ # ensure that your cursors get closed.
219
+ #
220
+ # @return [True]
221
+ def close
222
+ if @cursor_id
223
+ message = ByteBuffer.new([0, 0, 0, 0])
224
+ message.put_int(1)
225
+ message.put_long(@cursor_id)
226
+ @connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, "cursor.close()")
227
+ end
228
+ @cursor_id = 0
229
+ @closed = true
230
+ end
231
+
232
+ # Is this cursor closed?
233
+ #
234
+ # @return [Boolean]
235
+ def closed?; @closed; end
236
+
237
+ # Returns an integer indicating which query options have been selected.
238
+ #
239
+ # @return [Integer]
240
+ #
241
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
242
+ # The MongoDB wire protocol.
243
+ def query_opts
244
+ timeout = @timeout ? 0 : Mongo::Constants::OP_QUERY_NO_CURSOR_TIMEOUT
245
+ slave_ok = @connection.slave_ok? ? Mongo::Constants::OP_QUERY_SLAVE_OK : 0
246
+ slave_ok + timeout
247
+ end
248
+
249
+ # Get the query options for this Cursor.
250
+ #
251
+ # @return [Hash]
252
+ def query_options_hash
253
+ { :selector => @selector,
254
+ :fields => @fields,
255
+ :admin => @admin,
256
+ :skip => @skip_num,
257
+ :limit => @limit_num,
258
+ :order => @order,
259
+ :hint => @hint,
260
+ :snapshot => @snapshot,
261
+ :timeout => @timeout }
262
+ end
263
+
264
+ private
265
+
266
+ # Convert the +:fields+ parameter from a single field name or an array
267
+ # of fields names to a hash, with the field names for keys and '1' for each
268
+ # value.
269
+ def convert_fields_for_query(fields)
270
+ case fields
271
+ when String, Symbol
272
+ {fields => 1}
273
+ when Array
274
+ return nil if fields.length.zero?
275
+ returning({}) do |hash|
276
+ fields.each { |field| hash[field] = 1 }
277
+ end
278
+ end
279
+ end
280
+
281
+ # Set the query selector hash. If the selector is a Code or String object,
282
+ # the selector will be used in a $where clause.
283
+ # See http://www.mongodb.org/display/DOCS/Server-side+Code+Execution
284
+ def convert_selector_for_query(selector)
285
+ case selector
286
+ when Hash
287
+ selector
288
+ when nil
289
+ {}
290
+ when String
291
+ {"$where" => Code.new(selector)}
292
+ when Code
293
+ {"$where" => selector}
294
+ end
295
+ end
296
+
297
+ # Returns true if the query contains order, explain, hint, or snapshot.
298
+ def query_contains_special_fields?
299
+ @order || @explain || @hint || @snapshot
300
+ end
301
+
302
+ # Return a number of documents remaining for this cursor.
303
+ def num_remaining
304
+ refill_via_get_more if @cache.length == 0
305
+ @cache.length
306
+ end
307
+
308
+ # Internal method, not for general use. Return +true+ if there are
309
+ # more records to retrieve. This method does not check @limit;
310
+ # Cursor#each is responsible for doing that.
311
+ def more?
312
+ num_remaining > 0
313
+ end
314
+
315
+ def refill_via_get_more
316
+ return if send_initial_query || @cursor_id.zero?
317
+ message = ByteBuffer.new([0, 0, 0, 0])
318
+
319
+ # DB name.
320
+ db_name = @admin ? 'admin' : @db.name
321
+ BSON_RUBY.serialize_cstr(message, "#{db_name}.#{@collection.name}")
322
+
323
+ # Number of results to return; db decides for now.
324
+ message.put_int(0)
325
+
326
+ # Cursor id.
327
+ message.put_long(@cursor_id)
328
+ results, @n_received, @cursor_id = @connection.receive_message(Mongo::Constants::OP_GET_MORE, message, "cursor.get_more()", @socket)
329
+ @cache += results
330
+ close_cursor_if_query_complete
331
+ end
332
+
333
+ # Run query the first time we request an object from the wire
334
+ def send_initial_query
335
+ if @query_run
336
+ false
337
+ else
338
+ message = construct_query_message
339
+ results, @n_received, @cursor_id = @connection.receive_message(Mongo::Constants::OP_QUERY, message,
340
+ (query_log_message if @connection.logger), @socket)
341
+ @cache += results
342
+ @query_run = true
343
+ close_cursor_if_query_complete
344
+ true
345
+ end
346
+ end
347
+
348
+ def construct_query_message
349
+ message = ByteBuffer.new
350
+ message.put_int(query_opts)
351
+ db_name = @admin ? 'admin' : @db.name
352
+ BSON_RUBY.serialize_cstr(message, "#{db_name}.#{@collection.name}")
353
+ message.put_int(@skip)
354
+ message.put_int(@limit)
355
+ selector = @selector
356
+ if query_contains_special_fields?
357
+ selector = selector_with_special_query_fields
358
+ end
359
+ message.put_array(BSON.serialize(selector, false).to_a)
360
+ message.put_array(BSON.serialize(@fields, false).to_a) if @fields
361
+ message
362
+ end
363
+
364
+ def query_log_message
365
+ "#{@admin ? 'admin' : @db.name}.#{@collection.name}.find(#{@selector.inspect}, #{@fields ? @fields.inspect : '{}'})" +
366
+ "#{@skip != 0 ? ('.skip(' + @skip.to_s + ')') : ''}#{@limit != 0 ? ('.limit(' + @limit.to_s + ')') : ''}"
367
+ end
368
+
369
+ def selector_with_special_query_fields
370
+ sel = OrderedHash.new
371
+ sel['query'] = @selector
372
+ sel['orderby'] = formatted_sort_clause(@order) if @order
373
+ sel['$hint'] = @hint if @hint && @hint.length > 0
374
+ sel['$explain'] = true if @explain
375
+ sel['$snapshot'] = true if @snapshot
376
+ sel
377
+ end
378
+
379
+ def to_s
380
+ "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)"
381
+ end
382
+
383
+ def close_cursor_if_query_complete
384
+ close if @limit > 0 && @n_received >= @limit
385
+ end
386
+
387
+ def check_modifiable
388
+ if @query_run || @closed
389
+ raise InvalidOperation, "Cannot modify the query once it has been run or closed."
390
+ end
391
+ end
392
+ end
393
+ end
data/lib/mongo/db.rb ADDED
@@ -0,0 +1,527 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'socket'
18
+ require 'timeout'
19
+ require 'digest/md5'
20
+ require 'thread'
21
+
22
+ module Mongo
23
+
24
+ # A MongoDB database.
25
+ class DB
26
+
27
+ SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
28
+ SYSTEM_INDEX_COLLECTION = "system.indexes"
29
+ SYSTEM_PROFILE_COLLECTION = "system.profile"
30
+ SYSTEM_USER_COLLECTION = "system.users"
31
+ SYSTEM_COMMAND_COLLECTION = "$cmd"
32
+
33
+ # Counter for generating unique request ids.
34
+ @@current_request_id = 0
35
+
36
+ # Strict mode enforces collection existence checks. When +true+,
37
+ # asking for a collection that does not exist, or trying to create a
38
+ # collection that already exists, raises an error.
39
+ #
40
+ # Strict mode is disabled by default, but enabled (+true+) at any time.
41
+ attr_writer :strict
42
+
43
+ # Returns the value of the +strict+ flag.
44
+ def strict?; @strict; end
45
+
46
+ # The name of the database.
47
+ attr_reader :name
48
+
49
+ # The Mongo::Connection instance connecting to the MongoDB server.
50
+ attr_reader :connection
51
+
52
+ # Instances of DB are normally obtained by calling Mongo#db.
53
+ #
54
+ # @param [String] db_name the database name.
55
+ # @param [Mongo::Connection] connection a connection object pointing to MongoDB. Note
56
+ # that databases are usually instantiated via the Connection class. See the examples below.
57
+ #
58
+ # @option options [Boolean] strict (False) If true, collections must exist to be accessed and must
59
+ # not exist to be created. See DB#collection and DB#create_collection.
60
+ #
61
+ # @option options [Object, #create_pk(doc)] pk (Mongo::ObjectID) A primary key factory object,
62
+ # which should take a hash and return a hash which merges the original hash with any primary key
63
+ # fields the factory wishes to inject. (NOTE: if the object already has a primary key,
64
+ # the factory should not inject a new key).
65
+ def initialize(db_name, connection, options={})
66
+ @name = validate_db_name(db_name)
67
+ @connection = connection
68
+ @strict = options[:strict]
69
+ @pk_factory = options[:pk]
70
+ end
71
+
72
+ # Authenticate with the given username and password. Note that mongod
73
+ # must be started with the --auth option for authentication to be enabled.
74
+ #
75
+ # @param [String] username
76
+ # @param [String] password
77
+ #
78
+ # @return [Boolean]
79
+ def authenticate(username, password)
80
+ doc = command(:getnonce => 1)
81
+ raise "error retrieving nonce: #{doc}" unless ok?(doc)
82
+ nonce = doc['nonce']
83
+
84
+ auth = OrderedHash.new
85
+ auth['authenticate'] = 1
86
+ auth['user'] = username
87
+ auth['nonce'] = nonce
88
+ auth['key'] = Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
89
+ ok?(command(auth))
90
+ end
91
+
92
+ # Deauthorizes use for this database for this connection.
93
+ #
94
+ # @raise [MongoDBError] if logging out fails.
95
+ #
96
+ # @return [Boolean]
97
+ def logout
98
+ doc = command(:logout => 1)
99
+ return true if ok?(doc)
100
+ raise MongoDBError, "error logging out: #{doc.inspect}"
101
+ end
102
+
103
+ # Get an array of collection names in this database.
104
+ #
105
+ # @return [Array]
106
+ def collection_names
107
+ names = collections_info.collect { |doc| doc['name'] || '' }
108
+ names = names.delete_if {|name| name.index(@name).nil? || name.index('$')}
109
+ names.map {|name| name.sub(@name + '.', '')}
110
+ end
111
+
112
+ # Get an array of Collection instances, one for each collection in this database.
113
+ #
114
+ # @return [Array<Mongo::Collection>]
115
+ def collections
116
+ collection_names.map do |collection_name|
117
+ Collection.new(self, collection_name)
118
+ end
119
+ end
120
+
121
+ # Get info on system namespaces (collections). This method returns
122
+ # a cursor which can be iterated over. For each collection, a hash
123
+ # will be yielded containing a 'name' string and, optionally, an 'options' hash.
124
+ #
125
+ # @param [String] coll_name return info for the specifed collection only.
126
+ #
127
+ # @return [Mongo::Cursor]
128
+ def collections_info(coll_name=nil)
129
+ selector = {}
130
+ selector[:name] = full_collection_name(coll_name) if coll_name
131
+ Cursor.new(Collection.new(self, SYSTEM_NAMESPACE_COLLECTION), :selector => selector)
132
+ end
133
+
134
+ # Create a collection.
135
+ #
136
+ # new collection. If +strict+ is true, will raise an error if
137
+ # collection +name+ already exists.
138
+ #
139
+ # @param [String] name the name of the new collection.
140
+ #
141
+ # @option options [Boolean] :capped (False) created a capped collection.
142
+ #
143
+ # @option options [Integer] :size (Nil) If +capped+ is +true+, specifies the maximum number of
144
+ # bytes for the capped collection. If +false+, specifies the number of bytes allocated
145
+ # for the initial extent of the collection.
146
+ #
147
+ # @option options [Integer] :max (Nil) If +capped+ is +true+, indicates the maximum number of records
148
+ # in a capped collection.
149
+ #
150
+ # @raise [MongoDBError] raised under two conditions: either we're in +strict+ mode and the collection
151
+ # already exists or collection creation fails on the server.
152
+ #
153
+ # @return [Mongo::Collection]
154
+ def create_collection(name, options={})
155
+ # Does the collection already exist?
156
+ if collection_names.include?(name)
157
+ if strict?
158
+ raise MongoDBError, "Collection #{name} already exists. Currently in strict mode."
159
+ else
160
+ return Collection.new(self, name)
161
+ end
162
+ end
163
+
164
+ # Create a new collection.
165
+ oh = OrderedHash.new
166
+ oh[:create] = name
167
+ doc = command(oh.merge(options || {}))
168
+ ok = doc['ok']
169
+ return Collection.new(self, name, @pk_factory) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
170
+ raise MongoDBError, "Error creating collection: #{doc.inspect}"
171
+ end
172
+
173
+ # @deprecated all the admin methods are now included in the DB class.
174
+ def admin
175
+ warn "DB#admin has been DEPRECATED. All the admin functions are now available in the DB class itself."
176
+ Admin.new(self)
177
+ end
178
+
179
+ # Get a collection by name.
180
+ #
181
+ # @param [String] name the collection name.
182
+ #
183
+ # @raise [MongoDBError] if collection does not already exist and we're in +strict+ mode.
184
+ #
185
+ # @return [Mongo::Collection]
186
+ def collection(name)
187
+ return Collection.new(self, name, @pk_factory) if !strict? || collection_names.include?(name)
188
+ raise MongoDBError, "Collection #{name} doesn't exist. Currently in strict mode."
189
+ end
190
+ alias_method :[], :collection
191
+
192
+ # Drop a collection by +name+.
193
+ #
194
+ # @param [String] name
195
+ #
196
+ # @return [Boolean] True on success or if the collection names doesn't exist.
197
+ def drop_collection(name)
198
+ return true unless collection_names.include?(name)
199
+
200
+ ok?(command(:drop => name))
201
+ end
202
+
203
+ # Get the error message from the most recently executed database
204
+ # operation for this connection.
205
+ #
206
+ # @return [String, Nil] either the text describing the error or nil if no
207
+ # error has occurred.
208
+ def error
209
+ doc = command(:getlasterror => 1)
210
+ raise MongoDBError, "error retrieving last error: #{doc}" unless ok?(doc)
211
+ doc['err']
212
+ end
213
+
214
+ # Get status information from the last operation on this connection.
215
+ #
216
+ # @return [Hash] a hash representing the status of the last db op.
217
+ def last_status
218
+ command(:getlasterror => 1)
219
+ end
220
+
221
+ # Return +true+ if an error was caused by the most recently executed
222
+ # database operation.
223
+ #
224
+ # @return [Boolean]
225
+ def error?
226
+ error != nil
227
+ end
228
+
229
+ # Get the most recent error to have occured on this database.
230
+ #
231
+ # This command only returns errors that have occured since the last call to
232
+ # DB#reset_error_history - returns +nil+ if there is no such error.
233
+ #
234
+ # @return [String, Nil] the text of the error or +nil+ if no error has occurred.
235
+ def previous_error
236
+ error = command(:getpreverror => 1)
237
+ if error["err"]
238
+ error
239
+ else
240
+ nil
241
+ end
242
+ end
243
+
244
+ # Reset the error history of this database
245
+ #
246
+ # Calls to DB#previous_error will only return errors that have occurred
247
+ # since the most recent call to this method.
248
+ #
249
+ # @return [Hash]
250
+ def reset_error_history
251
+ command(:reseterror => 1)
252
+ end
253
+
254
+ # @deprecated please use Collection#find to create queries.
255
+ #
256
+ # Returns a Cursor over the query results.
257
+ #
258
+ # Note that the query gets sent lazily; the cursor calls
259
+ # Connection#send_message when needed. If the caller never requests an
260
+ # object from the cursor, the query never gets sent.
261
+ def query(collection, query, admin=false)
262
+ Cursor.new(self, collection, query, admin)
263
+ end
264
+
265
+ # Dereference a DBRef, returning the document it points to.
266
+ #
267
+ # @param [Mongo::DBRef] dbref
268
+ #
269
+ # @return [Hash] the document indicated by the db reference.
270
+ #
271
+ # @see http://www.mongodb.org/display/DOCS/DB+Ref MongoDB DBRef spec.
272
+ def dereference(dbref)
273
+ collection(dbref.namespace).find_one("_id" => dbref.object_id)
274
+ end
275
+
276
+ # Evaluate a JavaScript expression in MongoDB.
277
+ #
278
+ # @param [String, Code] code a JavaScript expression to evaluate server-side.
279
+ # @param [Integer, Hash] args any additional argument to be passed to the +code+ expression when
280
+ # it's run on the server.
281
+ #
282
+ # @return [String] the return value of the function.
283
+ def eval(code, *args)
284
+ if not code.is_a? Code
285
+ code = Code.new(code)
286
+ end
287
+
288
+ oh = OrderedHash.new
289
+ oh[:$eval] = code
290
+ oh[:args] = args
291
+ doc = command(oh)
292
+ return doc['retval'] if ok?(doc)
293
+ raise OperationFailure, "eval failed: #{doc['errmsg']}"
294
+ end
295
+
296
+ # Rename a collection.
297
+ #
298
+ # @param [String] from original collection name.
299
+ # @param [String] to new collection name.
300
+ #
301
+ # @return [True] returns +true+ on success.
302
+ #
303
+ # @raise MongoDBError if there's an error renaming the collection.
304
+ def rename_collection(from, to)
305
+ oh = OrderedHash.new
306
+ oh[:renameCollection] = "#{@name}.#{from}"
307
+ oh[:to] = "#{@name}.#{to}"
308
+ doc = command(oh, true)
309
+ ok?(doc) || raise(MongoDBError, "Error renaming collection: #{doc.inspect}")
310
+ end
311
+
312
+ # Drop an index from a given collection. Normally called from
313
+ # Collection#drop_index or Collection#drop_indexes.
314
+ #
315
+ # @param [String] collection_name
316
+ # @param [String] index_name
317
+ #
318
+ # @return [True] returns +true+ on success.
319
+ #
320
+ # @raise MongoDBError if there's an error renaming the collection.
321
+ def drop_index(collection_name, index_name)
322
+ oh = OrderedHash.new
323
+ oh[:deleteIndexes] = collection_name
324
+ oh[:index] = index_name
325
+ doc = command(oh)
326
+ ok?(doc) || raise(MongoDBError, "Error with drop_index command: #{doc.inspect}")
327
+ end
328
+
329
+ # Get information on the indexes for the given collection.
330
+ # Normally called by Collection#index_information.
331
+ #
332
+ # @param [String] collection_name
333
+ #
334
+ # @return [Hash] keys are index names and the values are lists of [key, direction] pairs
335
+ # defining the index.
336
+ def index_information(collection_name)
337
+ sel = {:ns => full_collection_name(collection_name)}
338
+ info = {}
339
+ Cursor.new(Collection.new(self, SYSTEM_INDEX_COLLECTION), :selector => sel).each { |index|
340
+ info[index['name']] = index['key'].map {|k| k}
341
+ }
342
+ info
343
+ end
344
+
345
+ # Create a new index on the given collection.
346
+ # Normally called by Collection#create_index.
347
+ #
348
+ # @param [String] collection_name
349
+ # @param [String, Array] field_or_spec either either a single field name
350
+ # or an array of [field name, direction] pairs. Directions should be specified as
351
+ # Mongo::ASCENDING or Mongo::DESCENDING.
352
+ # @param [Boolean] unique if +true+, the created index will enforce a uniqueness constraint.
353
+ #
354
+ # @return [String] the name of the index created.
355
+ def create_index(collection_name, field_or_spec, unique=false)
356
+ self.collection(collection_name).create_index(field_or_spec, unique)
357
+ end
358
+
359
+ # Return +true+ if the supplied +doc+ contains an 'ok' field with the value 1.
360
+ #
361
+ # @param [Hash] doc
362
+ #
363
+ # @return [Boolean]
364
+ def ok?(doc)
365
+ ok = doc['ok']
366
+ ok.kind_of?(Numeric) && ok.to_i == 1
367
+ end
368
+
369
+ # Send a command to the database.
370
+ #
371
+ # Note: DB commands must start with the "command" key. For this reason,
372
+ # any selector containing more than one key must be an OrderedHash.
373
+ #
374
+ # It may be of interest hat a command in MongoDB is technically a kind of query
375
+ # that occurs on the system command collection ($cmd).
376
+ #
377
+ # @param [OrderedHash, Hash] selector an OrderedHash, or a standard Hash with just one
378
+ # key, specifying the command to be performed.
379
+ #
380
+ # @param [Boolean] admin If +true+, the command will be executed on the admin
381
+ # collection.
382
+ #
383
+ # @param [Boolean] check_response If +true+, will raise an exception if the
384
+ # command fails.
385
+ #
386
+ # @param [Socket] sock a socket to use. This is mainly for internal use.
387
+ #
388
+ # @return [Hash]
389
+ def command(selector, admin=false, check_response=false, sock=nil)
390
+ raise MongoArgumentError, "command must be given a selector" unless selector.is_a?(Hash) && !selector.empty?
391
+ if selector.class.eql?(Hash) && selector.keys.length > 1
392
+ raise MongoArgumentError, "DB#command requires an OrderedHash when hash contains multiple keys"
393
+ end
394
+
395
+ result = Cursor.new(system_command_collection, :admin => admin,
396
+ :limit => -1, :selector => selector, :socket => sock).next_document
397
+
398
+ if check_response && !ok?(result)
399
+ raise OperationFailure, "Database command '#{selector.keys.first}' failed."
400
+ else
401
+ result
402
+ end
403
+ end
404
+
405
+ # @deprecated please use DB#command instead.
406
+ def db_command(*args)
407
+ warn "DB#db_command has been DEPRECATED. Please use DB#command instead."
408
+ command(args[0], args[1])
409
+ end
410
+
411
+ # A shortcut returning db plus dot plus collection name.
412
+ #
413
+ # @param [String] collection_name
414
+ #
415
+ # @return [String]
416
+ def full_collection_name(collection_name)
417
+ "#{@name}.#{collection_name}"
418
+ end
419
+
420
+ # The primary key factory object (or +nil+).
421
+ #
422
+ # @return [Object, Nil]
423
+ def pk_factory
424
+ @pk_factory
425
+ end
426
+
427
+ # Specify a primary key factory if not already set.
428
+ #
429
+ # @raise [MongoArgumentError] if the primary key factory has already been set.
430
+ def pk_factory=(pk_factory)
431
+ if @pk_factory
432
+ raise MongoArgumentError, "Cannot change primary key factory once it's been set"
433
+ end
434
+
435
+ @pk_factory = pk_factory
436
+ end
437
+
438
+ # Return the current database profiling level. If profiling is enabled, you can
439
+ # get the results using DB#profiling_info.
440
+ #
441
+ # @return [Symbol] :off, :slow_only, or :all
442
+ def profiling_level
443
+ oh = OrderedHash.new
444
+ oh[:profile] = -1
445
+ doc = command(oh)
446
+ raise "Error with profile command: #{doc.inspect}" unless ok?(doc) && doc['was'].kind_of?(Numeric)
447
+ case doc['was'].to_i
448
+ when 0
449
+ :off
450
+ when 1
451
+ :slow_only
452
+ when 2
453
+ :all
454
+ else
455
+ raise "Error: illegal profiling level value #{doc['was']}"
456
+ end
457
+ end
458
+
459
+ # Set this database's profiling level. If profiling is enabled, you can
460
+ # get the results using DB#profiling_info.
461
+ #
462
+ # @param [Symbol] level acceptable options are +:off+, +:slow_only+, or +:all+.
463
+ def profiling_level=(level)
464
+ oh = OrderedHash.new
465
+ oh[:profile] = case level
466
+ when :off
467
+ 0
468
+ when :slow_only
469
+ 1
470
+ when :all
471
+ 2
472
+ else
473
+ raise "Error: illegal profiling level value #{level}"
474
+ end
475
+ doc = command(oh)
476
+ ok?(doc) || raise(MongoDBError, "Error with profile command: #{doc.inspect}")
477
+ end
478
+
479
+ # Get the current profiling information.
480
+ #
481
+ # @return [Array] a list of documents containing profiling information.
482
+ def profiling_info
483
+ Cursor.new(Collection.new(self, DB::SYSTEM_PROFILE_COLLECTION), :selector => {}).to_a
484
+ end
485
+
486
+ # Validate a named collection.
487
+ #
488
+ # @param [String] name the collection name.
489
+ #
490
+ # @return [Hash] validation information.
491
+ #
492
+ # @raise [MongoDBError] if the command fails or there's a problem with the validation
493
+ # data, or if the collection is invalid.
494
+ def validate_collection(name)
495
+ doc = command(:validate => name)
496
+ raise MongoDBError, "Error with validate command: #{doc.inspect}" unless ok?(doc)
497
+ result = doc['result']
498
+ raise MongoDBError, "Error with validation data: #{doc.inspect}" unless result.kind_of?(String)
499
+ raise MongoDBError, "Error: invalid collection #{name}: #{doc.inspect}" if result =~ /\b(exception|corrupt)\b/i
500
+ doc
501
+ end
502
+
503
+ private
504
+
505
+ def hash_password(username, plaintext)
506
+ Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
507
+ end
508
+
509
+ def system_command_collection
510
+ Collection.new(self, SYSTEM_COMMAND_COLLECTION)
511
+ end
512
+
513
+ def validate_db_name(db_name)
514
+ unless [String, Symbol].include?(db_name.class)
515
+ raise TypeError, "db_name must be a string or symbol"
516
+ end
517
+
518
+ [" ", ".", "$", "/", "\\"].each do |invalid_char|
519
+ if db_name.include? invalid_char
520
+ raise InvalidName, "database names cannot contain the character '#{invalid_char}'"
521
+ end
522
+ end
523
+ raise InvalidName, "database name cannot be the empty string" if db_name.empty?
524
+ db_name
525
+ end
526
+ end
527
+ end