mongo-find_replace 0.18.3

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